From 7a2984713254e13f8c6dfce5a99909232310cae4 Mon Sep 17 00:00:00 2001 From: p_monsalvete Date: Thu, 27 Apr 2023 15:40:59 -0400 Subject: [PATCH 01/15] first attempt to energy system catalog. Not working --- .../data_models/energy_systems/archetype.py | 75 +++++++ .../data_models/energy_systems/content.py | 18 ++ .../energy_systems/distribution_system.py | 66 ++++++ .../energy_systems/emission_system.py | 41 ++++ .../energy_systems/generation_system.py | 130 ++++++++++++ .../energy_systems/montreal_custom_catalog.py | 64 ++++++ .../energy_systems/nrcan_catalog.py | 189 ------------------ .../energy_systems_catalog_factory.py | 13 +- .../montreal_custom_systems.xml | 5 + hub/data/energy_systems/nrcan.xml | 11 - 10 files changed, 405 insertions(+), 207 deletions(-) create mode 100644 hub/catalog_factories/data_models/energy_systems/archetype.py create mode 100644 hub/catalog_factories/data_models/energy_systems/content.py create mode 100644 hub/catalog_factories/data_models/energy_systems/distribution_system.py create mode 100644 hub/catalog_factories/data_models/energy_systems/emission_system.py create mode 100644 hub/catalog_factories/data_models/energy_systems/generation_system.py create mode 100644 hub/catalog_factories/energy_systems/montreal_custom_catalog.py delete mode 100644 hub/catalog_factories/energy_systems/nrcan_catalog.py create mode 100644 hub/data/energy_systems/montreal_custom_systems.xml delete mode 100644 hub/data/energy_systems/nrcan.xml diff --git a/hub/catalog_factories/data_models/energy_systems/archetype.py b/hub/catalog_factories/data_models/energy_systems/archetype.py new file mode 100644 index 00000000..cfba33a8 --- /dev/null +++ b/hub/catalog_factories/data_models/energy_systems/archetype.py @@ -0,0 +1,75 @@ +""" +Energy System catalog archetype +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 hub.catalog_factories.data_models.energy_systems.generation_system import GenerationSystem +from hub.catalog_factories.data_models.energy_systems.distribution_system import DistributionSystem +from hub.catalog_factories.data_models.energy_systems.emission_system import EmissionSystem + + +class Archetype: + def __init__(self, + lod, + name, + demand_types, + generation_system, + distribution_system, + emission_system): + + self._lod = lod + self._name = name + self._demand_types = demand_types + self._generation_system = generation_system + self._distribution_system = distribution_system + self._emission_system = emission_system + + @property + def lod(self): + """ + Get level of detail of the catalog + :return: string + """ + return self._lod + + @property + def name(self): + """ + Get name + :return: string + """ + return f'{self._name}_{self._lod}' + + @property + def demand_types(self): + """ + Get demand able to cover from [heating, cooling, domestic_hot_water, electricity] + :return: [string] + """ + return self._demand_types + + @property + def generation_system(self) -> GenerationSystem: + """ + Get generation system + :return: GenerationSystem + """ + return self._generation_system + + @property + def distribution_system(self) -> DistributionSystem: + """ + Get distribution system + :return: DistributionSystem + """ + return self._distribution_system + + @property + def emission_system(self) -> EmissionSystem: + """ + Get emission system + :return: EmissionSystem + """ + return self._emission_system diff --git a/hub/catalog_factories/data_models/energy_systems/content.py b/hub/catalog_factories/data_models/energy_systems/content.py new file mode 100644 index 00000000..5a0b61f3 --- /dev/null +++ b/hub/catalog_factories/data_models/energy_systems/content.py @@ -0,0 +1,18 @@ +""" +Energy System catalog content +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 Content: + def __init__(self, archetypes): + self._archetypes = archetypes + + @property + def archetypes(self): + """ + All archetypes in the catalog + """ + return self._archetypes diff --git a/hub/catalog_factories/data_models/energy_systems/distribution_system.py b/hub/catalog_factories/data_models/energy_systems/distribution_system.py new file mode 100644 index 00000000..a726e976 --- /dev/null +++ b/hub/catalog_factories/data_models/energy_systems/distribution_system.py @@ -0,0 +1,66 @@ +""" +Energy System catalog distribution system +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 DistributionSystem: + def __init__(self, system_type, distribution_power, supply_temperature, mass_flow, distribution_consumption, + heat_losses): + + self._type = system_type + self._distribution_power = distribution_power + self._supply_temperature = supply_temperature + self._mass_flow = mass_flow + self._distribution_consumption = distribution_consumption + self._heat_losses = heat_losses + + @property + def type(self): + """ + Get type from [air, water, refrigerant] + :return: string + """ + return self._type + + @property + def distribution_power(self): + """ + Get distribution_power (pump or fan) in W + :return: float + """ + return self._distribution_power + + @property + def supply_temperature(self): + """ + Get supply_temperature in degree Celsius + :return: float + """ + return self._supply_temperature + + @property + def mass_flow(self): + """ + Get mass_flow in kg/s + :return: float + """ + return self._mass_flow + + @property + def distribution_consumption(self): + """ + Get distribution_consumption in % over energy produced + :return: float + """ + return self._distribution_consumption + + @property + def heat_losses(self): + """ + Get heat_losses in % over energy produced + :return: float + """ + return self._heat_losses diff --git a/hub/catalog_factories/data_models/energy_systems/emission_system.py b/hub/catalog_factories/data_models/energy_systems/emission_system.py new file mode 100644 index 00000000..18f445f4 --- /dev/null +++ b/hub/catalog_factories/data_models/energy_systems/emission_system.py @@ -0,0 +1,41 @@ +""" +Energy System catalog emission system +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 hub.catalog_factories.data_models.usages.schedule import Schedule + + +class EmissionSystem: + def __init__(self, system_type, parasitic_energy_consumption, hvac_availability): + + self._type = system_type + self._parasitic_energy_consumption = parasitic_energy_consumption + self._hvac_availability = hvac_availability + + @property + def type(self): + """ + Get type + :return: string + """ + return self._type + + @property + def parasitic_energy_consumption(self): + """ + Get parasitic_energy_consumption in W + :return: float + """ + return self._parasitic_energy_consumption + + # todo: should be here??? Related to control and associated to usage!!! + @property + def hvac_availability(self) -> [Schedule]: + """ + Get hvac_availability in degree Celsius + :return: [Schedule] + """ + return self._hvac_availability diff --git a/hub/catalog_factories/data_models/energy_systems/generation_system.py b/hub/catalog_factories/data_models/energy_systems/generation_system.py new file mode 100644 index 00000000..2f34b0e8 --- /dev/null +++ b/hub/catalog_factories/data_models/energy_systems/generation_system.py @@ -0,0 +1,130 @@ +""" +Energy System catalog generation system +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 GenerationSystem: + def __init__(self, system_type, fuel_type, source_type, heat_power, cooling_power, + electricity_power, heat_efficiency, cooling_efficiency, electricity_efficiency, + source_temperature, source_mass_flow, storage_capacity, auxiliary_equipment): + + self._type = system_type + self._fuel_type = fuel_type + self._source_type = source_type + self._heat_power = heat_power + self._cooling_power = cooling_power + self._electricity_power = electricity_power + self._heat_efficiency = heat_efficiency + self._cooling_efficiency = cooling_efficiency + self._electricity_efficiency = electricity_efficiency + self._source_temperature = source_temperature + self._source_mass_flow = source_mass_flow + self._storage_capacity = storage_capacity + self._auxiliary_equipment = auxiliary_equipment + + @property + def type(self): + """ + Get type + :return: string + """ + return self._type + + @property + def fuel_type(self): + """ + Get fuel_type from [renewable, gas, diesel, electricity, wood, coal] + :return: string + """ + return self._fuel_type + + @property + def source_type(self): + """ + Get source_type from [air, water, geothermal, district_heating, grid, on_site_electricity] + :return: string + """ + return self._source_type + + @property + def heat_power(self): + """ + Get heat_power in W + :return: float + """ + return self._heat_power + + @property + def cooling_power(self): + """ + Get cooling_power in W + :return: float + """ + return self._cooling_power + + @property + def electricity_power(self): + """ + Get electricity_power in W + :return: float + """ + return self._electricity_power + + @property + def heat_efficiency(self): + """ + Get heat_efficiency + :return: float + """ + return self._heat_efficiency + + @property + def cooling_efficiency(self): + """ + Get cooling_efficiency + :return: float + """ + return self._cooling_efficiency + + @property + def electricity_efficiency(self): + """ + Get electricity_efficiency + :return: float + """ + return self._electricity_efficiency + + @property + def source_temperature(self): + """ + Get source_temperature in degree Celsius + :return: float + """ + return self._source_temperature + + @property + def source_mass_flow(self): + """ + Get source_mass_flow in kg/s + :return: float + """ + return self._source_mass_flow + + @property + def storage_capacity(self): + """ + Get storage_capacity in J + :return: float + """ + return self._storage_capacity + + @property + def auxiliary_equipment(self) -> GenerationSystem: + """ + Get auxiliary_equipment + :return: GenerationSystem + """ + return self._auxiliary_equipment diff --git a/hub/catalog_factories/energy_systems/montreal_custom_catalog.py b/hub/catalog_factories/energy_systems/montreal_custom_catalog.py new file mode 100644 index 00000000..2ac6daab --- /dev/null +++ b/hub/catalog_factories/energy_systems/montreal_custom_catalog.py @@ -0,0 +1,64 @@ +""" +Montreal custom 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 xmltodict + +from hub.catalog_factories.catalog import Catalog +from hub.catalog_factories.data_models.energy_systems.archetype import Archetype +from hub.catalog_factories.data_models.energy_systems.content import Content + + +class MontrealCustomCatalog(Catalog): + def __init__(self, path): + path = str(path / 'montreal_custom_systems.xml') + with open(path) as xml: + self._archetypes = xmltodict.parse(xml.read(), force_list='archetype') + + # store the full catalog data model in self._content + self._content = Content(self._load_archetypes()) + + def _load_archetypes(self): + _catalog_archetypes = [] + archetypes = self._archetypes['archetypes']['archetype'] + for archetype in archetypes: + name = archetype['@name'] + lod = float(archetype['@lod']) + demand_types = archetype['@demands'] + _catalog_archetypes.append(Archetype(lod, + name, + demand_types, + generation_system, + distribution_system, + emission_system)) + return _catalog_archetypes + + def names(self, category=None): + """ + Get the catalog elements names + :parm: for energy systems catalog category filter does nothing as there is only one category (archetypes) + """ + _names = {'archetypes': []} + for archetype in self._content.archetypes: + _names['archetypes'].append(archetype.name) + return _names + + def entries(self, category=None): + """ + Get the catalog elements + :parm: for energy systems catalog category filter does nothing as there is only one category (archetypes) + """ + return self._content + + 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 + raise IndexError(f"{name} doesn't exists in the catalog") diff --git a/hub/catalog_factories/energy_systems/nrcan_catalog.py b/hub/catalog_factories/energy_systems/nrcan_catalog.py deleted file mode 100644 index baf8a2c2..00000000 --- a/hub/catalog_factories/energy_systems/nrcan_catalog.py +++ /dev/null @@ -1,189 +0,0 @@ -""" -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 index 0bfc9459..24ba0488 100644 --- a/hub/catalog_factories/energy_systems_catalog_factory.py +++ b/hub/catalog_factories/energy_systems_catalog_factory.py @@ -1,5 +1,5 @@ """ -Usage catalog factory, publish the usage information +Energy Systems catalog factory, publish the energy systems 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 @@ -7,18 +7,18 @@ Project Coder Pilar Monsalvete Álvarez de Uribarri pilar.monsalvete@concordia.c from pathlib import Path from typing import TypeVar -from hub.catalog_factories.energy_systems.nrcan_catalog import NrcanCatalog +from hub.catalog_factories.energy_systems.montreal_custom_catalog import MontrealCustomCatalog from hub.hub_logger import logger from hub.helpers.utils import validate_import_export_type Catalog = TypeVar('Catalog') -class UsageCatalogFactory: +class EnergySystemsCatalogFactory: 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) + class_funcs = validate_import_export_type(EnergySystemsCatalogFactory) if self._catalog_type not in class_funcs: err_msg = f"Wrong import type. Valid functions include {class_funcs}" logger.error(err_msg) @@ -26,12 +26,11 @@ class UsageCatalogFactory: self._path = base_path @property - def _nrcan(self): + def _montreal_custom(self): """ Retrieve NRCAN catalog """ - # nrcan retrieves the data directly from github - return NrcanCatalog(self._path) + return MontrealCustomCatalog(self._path) @property def catalog(self) -> Catalog: diff --git a/hub/data/energy_systems/montreal_custom_systems.xml b/hub/data/energy_systems/montreal_custom_systems.xml new file mode 100644 index 00000000..87183ce5 --- /dev/null +++ b/hub/data/energy_systems/montreal_custom_systems.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/hub/data/energy_systems/nrcan.xml b/hub/data/energy_systems/nrcan.xml deleted file mode 100644 index d9843ae5..00000000 --- a/hub/data/energy_systems/nrcan.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - boiler_set.json - chiller_set.json - curves.json - furnace_set.json - heat_pumps.json - shw_set.json - pv.json - unitary_acs.json - From 53aa540b452f1e53b130ab7ecbeaa160e226a9fc Mon Sep 17 00:00:00 2001 From: p_monsalvete Date: Thu, 27 Apr 2023 16:21:17 -0400 Subject: [PATCH 02/15] some modifications in parameters after technical discussion --- .../data_models/energy_systems/archetype.py | 2 +- .../energy_systems/distribution_system.py | 22 +---------- .../energy_systems/emission_system.py | 16 +------- .../energy_systems/generation_system.py | 38 +++---------------- 4 files changed, 10 insertions(+), 68 deletions(-) diff --git a/hub/catalog_factories/data_models/energy_systems/archetype.py b/hub/catalog_factories/data_models/energy_systems/archetype.py index cfba33a8..89b3f016 100644 --- a/hub/catalog_factories/data_models/energy_systems/archetype.py +++ b/hub/catalog_factories/data_models/energy_systems/archetype.py @@ -40,7 +40,7 @@ class Archetype: Get name :return: string """ - return f'{self._name}_{self._lod}' + return f'{self._name}_lod{self._lod}' @property def demand_types(self): diff --git a/hub/catalog_factories/data_models/energy_systems/distribution_system.py b/hub/catalog_factories/data_models/energy_systems/distribution_system.py index a726e976..7d620282 100644 --- a/hub/catalog_factories/data_models/energy_systems/distribution_system.py +++ b/hub/catalog_factories/data_models/energy_systems/distribution_system.py @@ -7,13 +7,11 @@ Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca class DistributionSystem: - def __init__(self, system_type, distribution_power, supply_temperature, mass_flow, distribution_consumption, + def __init__(self, system_type, supply_temperature, distribution_consumption, heat_losses): self._type = system_type - self._distribution_power = distribution_power self._supply_temperature = supply_temperature - self._mass_flow = mass_flow self._distribution_consumption = distribution_consumption self._heat_losses = heat_losses @@ -25,14 +23,6 @@ class DistributionSystem: """ return self._type - @property - def distribution_power(self): - """ - Get distribution_power (pump or fan) in W - :return: float - """ - return self._distribution_power - @property def supply_temperature(self): """ @@ -41,18 +31,10 @@ class DistributionSystem: """ return self._supply_temperature - @property - def mass_flow(self): - """ - Get mass_flow in kg/s - :return: float - """ - return self._mass_flow - @property def distribution_consumption(self): """ - Get distribution_consumption in % over energy produced + Get distribution_consumption (pump of fan) in % over energy produced :return: float """ return self._distribution_consumption diff --git a/hub/catalog_factories/data_models/energy_systems/emission_system.py b/hub/catalog_factories/data_models/energy_systems/emission_system.py index 18f445f4..f103914b 100644 --- a/hub/catalog_factories/data_models/energy_systems/emission_system.py +++ b/hub/catalog_factories/data_models/energy_systems/emission_system.py @@ -5,15 +5,12 @@ Copyright © 2023 Concordia CERC group Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ -from hub.catalog_factories.data_models.usages.schedule import Schedule - class EmissionSystem: - def __init__(self, system_type, parasitic_energy_consumption, hvac_availability): + def __init__(self, system_type, parasitic_energy_consumption): self._type = system_type self._parasitic_energy_consumption = parasitic_energy_consumption - self._hvac_availability = hvac_availability @property def type(self): @@ -26,16 +23,7 @@ class EmissionSystem: @property def parasitic_energy_consumption(self): """ - Get parasitic_energy_consumption in W + Get parasitic_energy_consumption in ratio (W/W) :return: float """ return self._parasitic_energy_consumption - - # todo: should be here??? Related to control and associated to usage!!! - @property - def hvac_availability(self) -> [Schedule]: - """ - Get hvac_availability in degree Celsius - :return: [Schedule] - """ - return self._hvac_availability diff --git a/hub/catalog_factories/data_models/energy_systems/generation_system.py b/hub/catalog_factories/data_models/energy_systems/generation_system.py index 2f34b0e8..d3b41939 100644 --- a/hub/catalog_factories/data_models/energy_systems/generation_system.py +++ b/hub/catalog_factories/data_models/energy_systems/generation_system.py @@ -7,16 +7,12 @@ Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca class GenerationSystem: - def __init__(self, system_type, fuel_type, source_type, heat_power, cooling_power, - electricity_power, heat_efficiency, cooling_efficiency, electricity_efficiency, + def __init__(self, system_type, fuel_type, source_types, heat_efficiency, cooling_efficiency, electricity_efficiency, source_temperature, source_mass_flow, storage_capacity, auxiliary_equipment): self._type = system_type self._fuel_type = fuel_type - self._source_type = source_type - self._heat_power = heat_power - self._cooling_power = cooling_power - self._electricity_power = electricity_power + self._source_types = source_types self._heat_efficiency = heat_efficiency self._cooling_efficiency = cooling_efficiency self._electricity_efficiency = electricity_efficiency @@ -42,36 +38,12 @@ class GenerationSystem: return self._fuel_type @property - def source_type(self): + def source_types(self): """ Get source_type from [air, water, geothermal, district_heating, grid, on_site_electricity] - :return: string + :return: [string] """ - return self._source_type - - @property - def heat_power(self): - """ - Get heat_power in W - :return: float - """ - return self._heat_power - - @property - def cooling_power(self): - """ - Get cooling_power in W - :return: float - """ - return self._cooling_power - - @property - def electricity_power(self): - """ - Get electricity_power in W - :return: float - """ - return self._electricity_power + return self._source_types @property def heat_efficiency(self): From 688198a3fb46b2eaab76eeae2f8cef87dd30617b Mon Sep 17 00:00:00 2001 From: p_monsalvete Date: Fri, 28 Apr 2023 10:50:46 -0400 Subject: [PATCH 03/15] added first example of possible catalog.xml --- .../energy_systems/generation_system.py | 2 + .../montreal_custom_systems.xml | 130 +++++++++++++++++- 2 files changed, 127 insertions(+), 5 deletions(-) diff --git a/hub/catalog_factories/data_models/energy_systems/generation_system.py b/hub/catalog_factories/data_models/energy_systems/generation_system.py index d3b41939..2f5cfaa9 100644 --- a/hub/catalog_factories/data_models/energy_systems/generation_system.py +++ b/hub/catalog_factories/data_models/energy_systems/generation_system.py @@ -5,6 +5,8 @@ Copyright © 2023 Concordia CERC group Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ +from __future__ import annotations + class GenerationSystem: def __init__(self, system_type, fuel_type, source_types, heat_efficiency, cooling_efficiency, electricity_efficiency, diff --git a/hub/data/energy_systems/montreal_custom_systems.xml b/hub/data/energy_systems/montreal_custom_systems.xml index 87183ce5..f607dc6e 100644 --- a/hub/data/energy_systems/montreal_custom_systems.xml +++ b/hub/data/energy_systems/montreal_custom_systems.xml @@ -1,5 +1,125 @@ - - - - - \ No newline at end of file + + + + Fuel-fired water boiler with baseboards + + heating + domestic_hot_water + + 0.85 + 10 + 2 + true + + + Electrical resistance water boiler + + heating + domestic_hot_water + + 1 + 10 + 2 + true + + + Fuel-fired furnace + + heating + + 0.85 + 25 + 5 + false + + + Electrical resistance furnace + + heating + + 1 + 25 + 5 + false + + + Baseboards: hydronic with fuel boiler + + heating + + 0.85 + 10 + 5 + false + + + Electrical baseboards + + heating + + 1 + 0 + 0 + false + + + Air cooled DX with external condenser + + cooling + + 3.23 + 0 + 5 + false + + + Water cooled, water chiller + + cooling + + 3.23 + 10 + 5 + false + + + Air cooled DX + + cooling + + 3.23 + 0 + 5 + false + + + PV system + + electricity + + 0.22 + true + + + + + + 1 + 7 + + + + + 2 + 7 + + + + + 2 + 7 + 10 + + + + \ No newline at end of file From 42c39e771c05f120e97b6de351c53d1ebf551057 Mon Sep 17 00:00:00 2001 From: p_monsalvete Date: Fri, 28 Apr 2023 12:11:48 -0400 Subject: [PATCH 04/15] modifications in the systems catalog file --- .../montreal_custom_systems.xml | 120 ++++++++++++++++-- 1 file changed, 110 insertions(+), 10 deletions(-) diff --git a/hub/data/energy_systems/montreal_custom_systems.xml b/hub/data/energy_systems/montreal_custom_systems.xml index f607dc6e..a7f21025 100644 --- a/hub/data/energy_systems/montreal_custom_systems.xml +++ b/hub/data/energy_systems/montreal_custom_systems.xml @@ -23,39 +23,43 @@ true - Fuel-fired furnace + Fuel-fired furnace and fuel boiler for acs heating - + domestic_hot_water + 0.85 25 - 5 + NEEDED VALUE false - Electrical resistance furnace + Electrical resistance furnace and electrical boiler for acs heating + domestic_hot_water 1 25 - 5 + NEEDED VALUE false Baseboards: hydronic with fuel boiler heating + domestic_hot_water 0.85 10 - 5 + 2 false - Electrical baseboards + Electrical baseboards and electrical boiler for acs heating + domestic_hot_water 1 0 @@ -69,7 +73,7 @@ 3.23 0 - 5 + NEEDED VALUE false @@ -89,7 +93,7 @@ 3.23 0 - 5 + NEEDED VALUE false @@ -108,6 +112,13 @@ 7 + + + 1 + 7 + 10 + + 2 @@ -121,5 +132,94 @@ 10 + + + 1 + 8 + + + + + 1 + 8 + 10 + + + + + 2 + 8 + + + + + 2 + 8 + 10 + + + + + 3 + 4 + + + + + 3 + 4 + 10 + + + + + 4 + 4 + + + + + 4 + 4 + 10 + + + + + 8 + + + + + 8 + 10 + + + + + 5 + 8 + + + + + 5 + 8 + 10 + + + + + 6 + 8 + + + + + 6 + 8 + 10 + + - \ No newline at end of file + From 0a96b83c227fb8837e643af7f1b9fa4d4ee480b9 Mon Sep 17 00:00:00 2001 From: p_monsalvete Date: Fri, 28 Apr 2023 12:24:08 -0400 Subject: [PATCH 05/15] added generic energy system classes to cdm --- .../data_models/energy_systems/archetype.py | 6 +- .../energy_systems/distribution_system.py | 4 +- .../energy_systems/emission_system.py | 1 - .../energy_systems/generation_system.py | 3 +- hub/city_model_structure/building.py | 26 ++- .../generic_distribution_system.py | 81 +++++++ .../energy_systems/generic_emission_system.py | 30 +++ .../energy_systems/generic_energy_system.py | 87 +++++++ .../generic_generation_system.py | 220 ++++++++++++++++++ 9 files changed, 444 insertions(+), 14 deletions(-) create mode 100644 hub/city_model_structure/energy_systems/generic_distribution_system.py create mode 100644 hub/city_model_structure/energy_systems/generic_emission_system.py create mode 100644 hub/city_model_structure/energy_systems/generic_energy_system.py create mode 100644 hub/city_model_structure/energy_systems/generic_generation_system.py diff --git a/hub/catalog_factories/data_models/energy_systems/archetype.py b/hub/catalog_factories/data_models/energy_systems/archetype.py index 89b3f016..58b671b6 100644 --- a/hub/catalog_factories/data_models/energy_systems/archetype.py +++ b/hub/catalog_factories/data_models/energy_systems/archetype.py @@ -5,6 +5,8 @@ Copyright © 2023 Concordia CERC group Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ +from typing import Union + from hub.catalog_factories.data_models.energy_systems.generation_system import GenerationSystem from hub.catalog_factories.data_models.energy_systems.distribution_system import DistributionSystem from hub.catalog_factories.data_models.energy_systems.emission_system import EmissionSystem @@ -59,7 +61,7 @@ class Archetype: return self._generation_system @property - def distribution_system(self) -> DistributionSystem: + def distribution_system(self) -> Union[None, DistributionSystem]: """ Get distribution system :return: DistributionSystem @@ -67,7 +69,7 @@ class Archetype: return self._distribution_system @property - def emission_system(self) -> EmissionSystem: + def emission_system(self) -> Union[None, EmissionSystem]: """ Get emission system :return: EmissionSystem diff --git a/hub/catalog_factories/data_models/energy_systems/distribution_system.py b/hub/catalog_factories/data_models/energy_systems/distribution_system.py index 7d620282..6d4ddbc8 100644 --- a/hub/catalog_factories/data_models/energy_systems/distribution_system.py +++ b/hub/catalog_factories/data_models/energy_systems/distribution_system.py @@ -34,7 +34,7 @@ class DistributionSystem: @property def distribution_consumption(self): """ - Get distribution_consumption (pump of fan) in % over energy produced + Get distribution_consumption (pump of fan) in ratio over energy produced :return: float """ return self._distribution_consumption @@ -42,7 +42,7 @@ class DistributionSystem: @property def heat_losses(self): """ - Get heat_losses in % over energy produced + Get heat_losses in ratio over energy produced :return: float """ return self._heat_losses diff --git a/hub/catalog_factories/data_models/energy_systems/emission_system.py b/hub/catalog_factories/data_models/energy_systems/emission_system.py index f103914b..09776f3b 100644 --- a/hub/catalog_factories/data_models/energy_systems/emission_system.py +++ b/hub/catalog_factories/data_models/energy_systems/emission_system.py @@ -8,7 +8,6 @@ Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca class EmissionSystem: def __init__(self, system_type, parasitic_energy_consumption): - self._type = system_type self._parasitic_energy_consumption = parasitic_energy_consumption diff --git a/hub/catalog_factories/data_models/energy_systems/generation_system.py b/hub/catalog_factories/data_models/energy_systems/generation_system.py index 2f5cfaa9..b6d0eb3a 100644 --- a/hub/catalog_factories/data_models/energy_systems/generation_system.py +++ b/hub/catalog_factories/data_models/energy_systems/generation_system.py @@ -6,6 +6,7 @@ Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ from __future__ import annotations +from typing import Union class GenerationSystem: @@ -96,7 +97,7 @@ class GenerationSystem: return self._storage_capacity @property - def auxiliary_equipment(self) -> GenerationSystem: + def auxiliary_equipment(self) -> Union[None, GenerationSystem]: """ Get auxiliary_equipment :return: GenerationSystem diff --git a/hub/city_model_structure/building.py b/hub/city_model_structure/building.py index 327cc0c7..307354ff 100644 --- a/hub/city_model_structure/building.py +++ b/hub/city_model_structure/building.py @@ -16,6 +16,7 @@ from hub.city_model_structure.city_object import CityObject from hub.city_model_structure.building_demand.household import Household from hub.city_model_structure.building_demand.internal_zone import InternalZone from hub.city_model_structure.attributes.polyhedron import Polyhedron +from hub.city_model_structure.energy_systems.generic_energy_system import GenericEnergySystem class Building(CityObject): @@ -45,6 +46,7 @@ class Building(CityObject): self._appliances_electrical_demand = dict() self._domestic_hot_water_heat_demand = dict() self._eave_height = None + self._energy_systems = None self._grounds = [] self._roofs = [] self._walls = [] @@ -190,14 +192,6 @@ class Building(CityObject): if value is not None: self._basement_heated = int(value) - @property - def heated_volume(self): - """ - Raises not implemented error - """ - # todo: this need to be calculated based on the basement and attic heated values - raise NotImplementedError - @property def year_of_construction(self): """ @@ -458,3 +452,19 @@ class Building(CityObject): for usage in internal_zone.usages: _usage = f'{_usage}{usage.name}_{usage.percentage} ' return _usage.rstrip() + + @property + def energy_systems(self) -> Union[None, List[GenericEnergySystem]]: + """ + Get list of energy systems installed to cover the building demands + :return: [GenericEnergySystem] + """ + return self._energy_systems + + @energy_systems.setter + def energy_systems(self, value): + """ + Set list of energy systems installed to cover the building demands + :param value: [GenericEnergySystem] + """ + self._energy_systems = value diff --git a/hub/city_model_structure/energy_systems/generic_distribution_system.py b/hub/city_model_structure/energy_systems/generic_distribution_system.py new file mode 100644 index 00000000..48135406 --- /dev/null +++ b/hub/city_model_structure/energy_systems/generic_distribution_system.py @@ -0,0 +1,81 @@ +""" +Generic energy distribution system definition +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 GenericDistributionSystem: + """ + GenericDistributionSystem class + """ + def __init__(self): + self._type = None + self._supply_temperature = None + self._distribution_consumption = None + self._heat_losses = None + + @property + def type(self): + """ + Get type from [air, water, refrigerant] + :return: string + """ + return self._type + + @type.setter + def type(self, value): + """ + Set type from [air, water, refrigerant] + :param value: string + """ + self._type = value + + @property + def supply_temperature(self): + """ + Get supply_temperature in degree Celsius + :return: float + """ + return self._supply_temperature + + @supply_temperature.setter + def supply_temperature(self, value): + """ + Set supply_temperature in degree Celsius + :param value: float + """ + self._supply_temperature = value + + @property + def distribution_consumption(self): + """ + Get distribution_consumption (pump of fan) in ratio over energy produced + :return: float + """ + return self._distribution_consumption + + @distribution_consumption.setter + def distribution_consumption(self, value): + """ + Set distribution_consumption (pump of fan) in ratio over energy produced + :param value: float + """ + self._distribution_consumption = value + + @property + def heat_losses(self): + """ + Get heat_losses in ratio over energy produced + :return: float + """ + return self._heat_losses + + @heat_losses.setter + def heat_losses(self, value): + """ + Set heat_losses in ratio over energy produced + :param value: float + """ + self._heat_losses = value diff --git a/hub/city_model_structure/energy_systems/generic_emission_system.py b/hub/city_model_structure/energy_systems/generic_emission_system.py new file mode 100644 index 00000000..dc85a1ab --- /dev/null +++ b/hub/city_model_structure/energy_systems/generic_emission_system.py @@ -0,0 +1,30 @@ +""" +Generic energy emission system definition +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 GenericEmissionSystem: + """ + GenericEmissionSystem class + """ + def __init__(self): + self._parasitic_energy_consumption = None + + @property + def parasitic_energy_consumption(self): + """ + Get parasitic_energy_consumption in ratio (W/W) + :return: float + """ + return self._parasitic_energy_consumption + + @parasitic_energy_consumption.setter + def parasitic_energy_consumption(self, value): + """ + Set parasitic_energy_consumption in ratio (W/W) + :param value: float + """ + self._parasitic_energy_consumption = value diff --git a/hub/city_model_structure/energy_systems/generic_energy_system.py b/hub/city_model_structure/energy_systems/generic_energy_system.py new file mode 100644 index 00000000..5fd2f1e5 --- /dev/null +++ b/hub/city_model_structure/energy_systems/generic_energy_system.py @@ -0,0 +1,87 @@ +""" +Generic energy system definition +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 + +from hub.city_model_structure.energy_systems.generic_generation_system import GenericGenerationSystem +from hub.city_model_structure.energy_systems.generic_distribution_system import GenericDistributionSystem +from hub.city_model_structure.energy_systems.generic_emission_system import GenericEmissionSystem + + +class GenericEnergySystem: + """ + GenericEnergySystem class + """ + def __init__(self): + self._demand_types = None + self._generation_system = None + self._distribution_system = None + self._emission_system = None + + @property + def demand_types(self): + """ + Get demand able to cover from [heating, cooling, domestic_hot_water, electricity] + :return: [string] + """ + return self._demand_types + + @demand_types.setter + def demand_types(self, value): + """ + Set demand able to cover from [heating, cooling, domestic_hot_water, electricity] + :param value: [string] + """ + self._demand_types = value + + @property + def generation_system(self) -> GenericGenerationSystem: + """ + Get generation system + :return: GenerationSystem + """ + return self._generation_system + + @generation_system.setter + def generation_system(self, value): + """ + Set generation system + :param value: GenerationSystem + """ + self._generation_system = value + + @property + def distribution_system(self) -> Union[None, GenericDistributionSystem]: + """ + Get distribution system + :return: DistributionSystem + """ + return self._distribution_system + + @distribution_system.setter + def distribution_system(self, value): + """ + Set distribution system + :param value: DistributionSystem + """ + self._distribution_system = value + + @property + def emission_system(self) -> Union[None, GenericEmissionSystem]: + """ + Get emission system + :return: EmissionSystem + """ + return self._emission_system + + @emission_system.setter + def emission_system(self, value): + """ + Set emission system + :param value: EmissionSystem + """ + self._emission_system = value diff --git a/hub/city_model_structure/energy_systems/generic_generation_system.py b/hub/city_model_structure/energy_systems/generic_generation_system.py new file mode 100644 index 00000000..72c5fb9e --- /dev/null +++ b/hub/city_model_structure/energy_systems/generic_generation_system.py @@ -0,0 +1,220 @@ +""" +Generic energy generation system definition +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 __future__ import annotations +from typing import Union + + +class GenericGenerationSystem: + """ + GenericGenerationSystem class + """ + def __init__(self): + self._fuel_type = None + self._heat_power = None + self._cooling_power = None + self._electricity_power = None + self._source_types = None + self._heat_efficiency = None + self._cooling_efficiency = None + self._electricity_efficiency = None + self._source_temperature = None + self._source_mass_flow = None + self._storage_capacity = None + self._auxiliary_equipment = None + + @property + def fuel_type(self): + """ + Get fuel_type from [renewable, gas, diesel, electricity, wood, coal] + :return: string + """ + return self._fuel_type + + @fuel_type.setter + def fuel_type(self, value): + """ + Set fuel_type from [renewable, gas, diesel, electricity, wood, coal] + :param value: string + """ + self._fuel_type = value + + @property + def source_types(self): + """ + Get source_type from [air, water, geothermal, district_heating, grid, on_site_electricity] + :return: [string] + """ + return self._source_types + + @source_types.setter + def source_types(self, value): + """ + Set source_type from [air, water, geothermal, district_heating, grid, on_site_electricity] + :param value: [string] + """ + self._source_types = value + + @property + def heat_power(self): + """ + Get heat_power in W + :return: float + """ + return self._heat_power + + @heat_power.setter + def heat_power(self, value): + """ + Set heat_power in W + :param value: float + """ + self._heat_power = value + + @property + def cooling_power(self): + """ + Get cooling_power in W + :return: float + """ + return self._cooling_power + + @cooling_power.setter + def cooling_power(self, value): + """ + Set cooling_power in W + :param value: float + """ + self._cooling_power = value + + @property + def electricity_power(self): + """ + Get electricity_power in W + :return: float + """ + return self._electricity_power + + @electricity_power.setter + def electricity_power(self, value): + """ + Set electricity_power in W + :param value: float + """ + self._electricity_power = value + + @property + def heat_efficiency(self): + """ + Get heat_efficiency + :return: float + """ + return self._heat_efficiency + + @heat_efficiency.setter + def heat_efficiency(self, value): + """ + Set heat_efficiency + :param value: float + """ + self._heat_efficiency = value + + @property + def cooling_efficiency(self): + """ + Get cooling_efficiency + :return: float + """ + return self._cooling_efficiency + + @cooling_efficiency.setter + def cooling_efficiency(self, value): + """ + Set cooling_efficiency + :param value: float + """ + self._cooling_efficiency = value + + @property + def electricity_efficiency(self): + """ + Get electricity_efficiency + :return: float + """ + return self._electricity_efficiency + + @electricity_efficiency.setter + def electricity_efficiency(self, value): + """ + Set electricity_efficiency + :param value: float + """ + self._electricity_efficiency = value + + @property + def source_temperature(self): + """ + Get source_temperature in degree Celsius + :return: float + """ + return self._source_temperature + + @source_temperature.setter + def source_temperature(self, value): + """ + Set source_temperature in degree Celsius + :param value: float + """ + self._source_temperature = value + + @property + def source_mass_flow(self): + """ + Get source_mass_flow in kg/s + :return: float + """ + return self._source_mass_flow + + @source_mass_flow.setter + def source_mass_flow(self, value): + """ + Set source_mass_flow in kg/s + :param value: float + """ + self._source_mass_flow = value + + @property + def storage_capacity(self): + """ + Get storage_capacity in J + :return: float + """ + return self._storage_capacity + + @storage_capacity.setter + def storage_capacity(self, value): + """ + Set storage_capacity in J + :param value: float + """ + self._storage_capacity = value + + @property + def auxiliary_equipment(self) -> Union[None, GenericGenerationSystem]: + """ + Get auxiliary_equipment + :return: GenerationSystem + """ + return self._auxiliary_equipment + + @auxiliary_equipment.setter + def auxiliary_equipment(self, value): + """ + Set auxiliary_equipment + :param value: GenerationSystem + """ + self._auxiliary_equipment = value From 3bfb985c04c50f7689a786e9f5e8b7a2b530e610 Mon Sep 17 00:00:00 2001 From: p_monsalvete Date: Fri, 28 Apr 2023 14:07:07 -0400 Subject: [PATCH 06/15] finished system catalog factory --- .../data_models/energy_systems/archetype.py | 51 ++------ .../data_models/energy_systems/content.py | 12 +- .../data_models/energy_systems/equipment.py | 87 +++++++++++++ .../energy_systems/generation_system.py | 12 +- .../energy_systems/montreal_custom_catalog.py | 120 +++++++++++++++--- .../montreal_custom_systems.xml | 10 +- hub/unittests/test_systems_catalog.py | 30 +++++ 7 files changed, 246 insertions(+), 76 deletions(-) create mode 100644 hub/catalog_factories/data_models/energy_systems/equipment.py create mode 100644 hub/unittests/test_systems_catalog.py diff --git a/hub/catalog_factories/data_models/energy_systems/archetype.py b/hub/catalog_factories/data_models/energy_systems/archetype.py index 58b671b6..b770eae1 100644 --- a/hub/catalog_factories/data_models/energy_systems/archetype.py +++ b/hub/catalog_factories/data_models/energy_systems/archetype.py @@ -5,28 +5,17 @@ Copyright © 2023 Concordia CERC group Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ -from typing import Union +from typing import List -from hub.catalog_factories.data_models.energy_systems.generation_system import GenerationSystem -from hub.catalog_factories.data_models.energy_systems.distribution_system import DistributionSystem -from hub.catalog_factories.data_models.energy_systems.emission_system import EmissionSystem +from hub.catalog_factories.data_models.energy_systems.equipment import Equipment class Archetype: - def __init__(self, - lod, - name, - demand_types, - generation_system, - distribution_system, - emission_system): + def __init__(self, lod, name, equipments): self._lod = lod self._name = name - self._demand_types = demand_types - self._generation_system = generation_system - self._distribution_system = distribution_system - self._emission_system = emission_system + self._equipments = equipments @property def lod(self): @@ -45,33 +34,9 @@ class Archetype: return f'{self._name}_lod{self._lod}' @property - def demand_types(self): + def equipments(self) -> List[Equipment]: """ - Get demand able to cover from [heating, cooling, domestic_hot_water, electricity] - :return: [string] + Get list of equipments that compose the total energy system + :return: [Equipment] """ - return self._demand_types - - @property - def generation_system(self) -> GenerationSystem: - """ - Get generation system - :return: GenerationSystem - """ - return self._generation_system - - @property - def distribution_system(self) -> Union[None, DistributionSystem]: - """ - Get distribution system - :return: DistributionSystem - """ - return self._distribution_system - - @property - def emission_system(self) -> Union[None, EmissionSystem]: - """ - Get emission system - :return: EmissionSystem - """ - return self._emission_system + return self._equipments diff --git a/hub/catalog_factories/data_models/energy_systems/content.py b/hub/catalog_factories/data_models/energy_systems/content.py index 5a0b61f3..53c07efb 100644 --- a/hub/catalog_factories/data_models/energy_systems/content.py +++ b/hub/catalog_factories/data_models/energy_systems/content.py @@ -7,12 +7,20 @@ Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca class Content: - def __init__(self, archetypes): + def __init__(self, archetypes, equipments): self._archetypes = archetypes + self._equipments = equipments @property def archetypes(self): """ - All archetypes in the catalog + All archetype systems in the catalog """ return self._archetypes + + @property + def equipments(self): + """ + All equipments in the catalog + """ + return self._equipments diff --git a/hub/catalog_factories/data_models/energy_systems/equipment.py b/hub/catalog_factories/data_models/energy_systems/equipment.py new file mode 100644 index 00000000..9b2ddcd4 --- /dev/null +++ b/hub/catalog_factories/data_models/energy_systems/equipment.py @@ -0,0 +1,87 @@ +""" +Energy System catalog equipment +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 + +from hub.catalog_factories.data_models.energy_systems.generation_system import GenerationSystem +from hub.catalog_factories.data_models.energy_systems.distribution_system import DistributionSystem +from hub.catalog_factories.data_models.energy_systems.emission_system import EmissionSystem + + +class Equipment: + def __init__(self, + lod, + equipment_id, + name, + demand_types, + generation_system, + distribution_system, + emission_system): + + self._lod = lod + self._equipment_id = equipment_id + self._name = name + self._demand_types = demand_types + self._generation_system = generation_system + self._distribution_system = distribution_system + self._emission_system = emission_system + + @property + def lod(self): + """ + Get level of detail of the catalog + :return: string + """ + return self._lod + + @property + def id(self): + """ + Get equipment id + :return: string + """ + return self._equipment_id + + @property + def name(self): + """ + Get name + :return: string + """ + return f'{self._name}_lod{self._lod}' + + @property + def demand_types(self): + """ + Get demand able to cover from [heating, cooling, domestic_hot_water, electricity] + :return: [string] + """ + return self._demand_types + + @property + def generation_system(self) -> GenerationSystem: + """ + Get generation system + :return: GenerationSystem + """ + return self._generation_system + + @property + def distribution_system(self) -> Union[None, DistributionSystem]: + """ + Get distribution system + :return: DistributionSystem + """ + return self._distribution_system + + @property + def emission_system(self) -> Union[None, EmissionSystem]: + """ + Get emission system + :return: EmissionSystem + """ + return self._emission_system diff --git a/hub/catalog_factories/data_models/energy_systems/generation_system.py b/hub/catalog_factories/data_models/energy_systems/generation_system.py index b6d0eb3a..51a5325c 100644 --- a/hub/catalog_factories/data_models/energy_systems/generation_system.py +++ b/hub/catalog_factories/data_models/energy_systems/generation_system.py @@ -11,7 +11,7 @@ from typing import Union class GenerationSystem: def __init__(self, system_type, fuel_type, source_types, heat_efficiency, cooling_efficiency, electricity_efficiency, - source_temperature, source_mass_flow, storage_capacity, auxiliary_equipment): + source_temperature, source_mass_flow, storage, auxiliary_equipment): self._type = system_type self._fuel_type = fuel_type @@ -21,7 +21,7 @@ class GenerationSystem: self._electricity_efficiency = electricity_efficiency self._source_temperature = source_temperature self._source_mass_flow = source_mass_flow - self._storage_capacity = storage_capacity + self._storage = storage self._auxiliary_equipment = auxiliary_equipment @property @@ -89,12 +89,12 @@ class GenerationSystem: return self._source_mass_flow @property - def storage_capacity(self): + def storage(self): """ - Get storage_capacity in J - :return: float + Get boolean storage exists + :return: bool """ - return self._storage_capacity + return self._storage @property def auxiliary_equipment(self) -> Union[None, GenerationSystem]: diff --git a/hub/catalog_factories/energy_systems/montreal_custom_catalog.py b/hub/catalog_factories/energy_systems/montreal_custom_catalog.py index 2ac6daab..d42601b2 100644 --- a/hub/catalog_factories/energy_systems/montreal_custom_catalog.py +++ b/hub/catalog_factories/energy_systems/montreal_custom_catalog.py @@ -8,50 +8,127 @@ 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.energy_systems.archetype import Archetype +from hub.catalog_factories.data_models.energy_systems.equipment import Equipment from hub.catalog_factories.data_models.energy_systems.content import Content +from hub.catalog_factories.data_models.energy_systems.generation_system import GenerationSystem +from hub.catalog_factories.data_models.energy_systems.distribution_system import DistributionSystem +from hub.catalog_factories.data_models.energy_systems.archetype import Archetype class MontrealCustomCatalog(Catalog): def __init__(self, path): path = str(path / 'montreal_custom_systems.xml') with open(path) as xml: - self._archetypes = xmltodict.parse(xml.read(), force_list='archetype') + self._archetypes = xmltodict.parse(xml.read(), force_list=('equipment', 'system', 'demand', 'equipment_id')) + + self._lod = float(self._archetypes['catalog']['@lod']) + + self._catalog_equipments = self._load_equipments() + self._catalog_archetypes = self._load_archetypes() # store the full catalog data model in self._content - self._content = Content(self._load_archetypes()) + self._content = Content(self._catalog_archetypes, + self._catalog_equipments) + + def _load_equipments(self): + _catalog_equipments = [] + equipments = self._archetypes['catalog']['equipments']['equipment'] + for equipment in equipments: + equipment_id = float(equipment['@id']) + equipment_type = equipment['@type'] + fuel_type = equipment['@fuel_type'] + name = equipment['name'] + demands = equipment['demands']['demand'] + heating_efficiency = None + if 'heating_efficiency' in equipment: + heating_efficiency = float(equipment['heating_efficiency']) + cooling_efficiency = None + if 'cooling_efficiency' in equipment: + cooling_efficiency = float(equipment['cooling_efficiency']) + electricity_efficiency = None + if 'electricity_efficiency' in equipment: + electricity_efficiency = float(equipment['electricity_efficiency']) + distribution_heat_losses = None + if 'distribution_heat_losses' in equipment: + distribution_heat_losses = float(equipment['distribution_heat_losses']['#text']) / 100 + distribution_consumption = None + if 'distribution_consumption' in equipment: + distribution_consumption = float(equipment['distribution_consumption']['#text']) / 100 + storage = eval(equipment['storage'].capitalize()) + generation_system = GenerationSystem(equipment_type, + fuel_type, + None, + heating_efficiency, + cooling_efficiency, + electricity_efficiency, + None, + None, + storage, + None) + distribution_system = DistributionSystem(None, + None, + distribution_consumption, + distribution_heat_losses) + _catalog_equipments.append(Equipment(self._lod, + equipment_id, + name, + demands, + generation_system, + distribution_system, + None)) + return _catalog_equipments def _load_archetypes(self): _catalog_archetypes = [] - archetypes = self._archetypes['archetypes']['archetype'] - for archetype in archetypes: - name = archetype['@name'] - lod = float(archetype['@lod']) - demand_types = archetype['@demands'] - _catalog_archetypes.append(Archetype(lod, - name, - demand_types, - generation_system, - distribution_system, - emission_system)) + systems = self._archetypes['catalog']['systems']['system'] + for system in systems: + name = system['@name'] + system_equipments = system['equipments']['equipment_id'] + _equipments = [] + for system_equipment in system_equipments: + for equipment in self._catalog_equipments: + if int(equipment.id) == int(system_equipment): + _equipments.append(equipment) + _catalog_archetypes.append(Archetype(self._lod, name, _equipments)) return _catalog_archetypes def names(self, category=None): """ Get the catalog elements names - :parm: for energy systems catalog category filter does nothing as there is only one category (archetypes) + :parm: optional category filter """ - _names = {'archetypes': []} - for archetype in self._content.archetypes: - _names['archetypes'].append(archetype.name) + if category is None: + _names = {'archetypes': [], 'equipments': []} + for archetype in self._content.archetypes: + _names['archetypes'].append(archetype.name) + for equipment in self._content.equipments: + _names['equipments'].append(equipment.name) + else: + _names = {category: []} + if category.lower() == 'archetypes': + for archetype in self._content.archetypes: + _names[category].append(archetype.name) + elif category.lower() == 'equipments': + for equipment in self._content.equipments: + _names[category].append(equipment.name) + else: + raise ValueError(f'Unknown category [{category}]') return _names def entries(self, category=None): """ Get the catalog elements - :parm: for energy systems catalog category filter does nothing as there is only one category (archetypes) + :parm: optional category filter """ - return self._content + if category is None: + return self._content + else: + if category.lower() == 'archetypes': + return self._content.archetypes + elif category.lower() == 'equipments': + return self._content.equipments + else: + raise ValueError(f'Unknown category [{category}]') def get_entry(self, name): """ @@ -61,4 +138,7 @@ class MontrealCustomCatalog(Catalog): for entry in self._content.archetypes: if entry.name.lower() == name.lower(): return entry + for entry in self._content.equipments: + if entry.name.lower() == name.lower(): + return entry raise IndexError(f"{name} doesn't exists in the catalog") diff --git a/hub/data/energy_systems/montreal_custom_systems.xml b/hub/data/energy_systems/montreal_custom_systems.xml index a7f21025..65c03767 100644 --- a/hub/data/energy_systems/montreal_custom_systems.xml +++ b/hub/data/energy_systems/montreal_custom_systems.xml @@ -30,7 +30,7 @@ 0.85 25 - NEEDED VALUE + 100000 false @@ -41,7 +41,7 @@ 1 25 - NEEDED VALUE + 100000 false @@ -73,7 +73,7 @@ 3.23 0 - NEEDED VALUE + 100000 false @@ -93,7 +93,7 @@ 3.23 0 - NEEDED VALUE + 100000 false @@ -214,7 +214,7 @@ 8 - + 6 8 diff --git a/hub/unittests/test_systems_catalog.py b/hub/unittests/test_systems_catalog.py new file mode 100644 index 00000000..da1f8d01 --- /dev/null +++ b/hub/unittests/test_systems_catalog.py @@ -0,0 +1,30 @@ +""" +TestSystemsCatalog +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +""" + +from unittest import TestCase +from hub.catalog_factories.energy_systems_catalog_factory import EnergySystemsCatalogFactory + + +class TestSystemsCatalog(TestCase): + + def test_montreal_custom_catalog(self): + catalog = EnergySystemsCatalogFactory('montreal_custom').catalog + catalog_categories = catalog.names() + archetypes = catalog.names('archetypes') + self.assertEqual(18, len(archetypes['archetypes'])) + equipments = catalog.names('equipments') + self.assertEqual(10, len(equipments['equipments'])) + with self.assertRaises(ValueError): + catalog.names('unknown') + + # retrieving all the entries should not raise any exceptions + for category in catalog_categories: + for value in catalog_categories[category]: + catalog.get_entry(value) + + with self.assertRaises(IndexError): + catalog.get_entry('unknown') From d5196fc54f68197684c7d80f4c2259303cb0e797 Mon Sep 17 00:00:00 2001 From: p_monsalvete Date: Fri, 28 Apr 2023 14:14:18 -0400 Subject: [PATCH 07/15] bug dividing by 0 in nrcan_usage_parameters.py solved --- hub/imports/usage/nrcan_usage_parameters.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/hub/imports/usage/nrcan_usage_parameters.py b/hub/imports/usage/nrcan_usage_parameters.py index 5898f2a0..8073ca67 100644 --- a/hub/imports/usage/nrcan_usage_parameters.py +++ b/hub/imports/usage/nrcan_usage_parameters.py @@ -130,11 +130,16 @@ class NrcanUsageParameters: def _assign_comnet_extra_values(usage, archetype, occupancy_density): _occupancy = usage.occupancy archetype_density = archetype.occupancy.occupancy_density - _occupancy.sensible_radiative_internal_gain = archetype.occupancy.sensible_radiative_internal_gain \ - * occupancy_density / archetype_density - _occupancy.latent_internal_gain = archetype.occupancy.latent_internal_gain * occupancy_density / archetype_density - _occupancy.sensible_convective_internal_gain = archetype.occupancy.sensible_convective_internal_gain \ - * occupancy_density / archetype_density + if archetype_density == 0: + _occupancy.sensible_radiative_internal_gain = 0 + _occupancy.latent_internal_gain = 0 + _occupancy.sensible_convective_internal_gain = 0 + else: + _occupancy.sensible_radiative_internal_gain = archetype.occupancy.sensible_radiative_internal_gain \ + * occupancy_density / archetype_density + _occupancy.latent_internal_gain = archetype.occupancy.latent_internal_gain * occupancy_density / archetype_density + _occupancy.sensible_convective_internal_gain = archetype.occupancy.sensible_convective_internal_gain \ + * occupancy_density / archetype_density @staticmethod def _calculate_reduced_values_from_extended_library(usage, archetype): From 35b0e8c872d7889678f8bbbb90263158cb1aca46 Mon Sep 17 00:00:00 2001 From: p_monsalvete Date: Fri, 28 Apr 2023 16:31:53 -0400 Subject: [PATCH 08/15] almost finished energy system importer, missing sizing --- hub/city_model_structure/building.py | 17 ++++ hub/city_model_structure/level_of_detail.py | 18 +++- .../construction/nrcan_physics_parameters.py | 10 +- .../construction/nrel_physics_parameters.py | 14 +-- hub/imports/construction_factory.py | 12 +-- ...ontreal_custom_energy_system_parameters.py | 98 +++++++++++++++++++ hub/imports/energy_systems_factory.py | 8 ++ hub/imports/usage/comnet_usage_parameters.py | 3 +- hub/imports/usage/nrcan_usage_parameters.py | 3 +- hub/imports/usage_factory.py | 11 +-- hub/unittests/test_systems_catalog.py | 3 + 11 files changed, 164 insertions(+), 33 deletions(-) create mode 100644 hub/imports/energy_systems/montreal_custom_energy_system_parameters.py diff --git a/hub/city_model_structure/building.py b/hub/city_model_structure/building.py index 307354ff..4e204065 100644 --- a/hub/city_model_structure/building.py +++ b/hub/city_model_structure/building.py @@ -47,6 +47,7 @@ class Building(CityObject): self._domestic_hot_water_heat_demand = dict() self._eave_height = None self._energy_systems = None + self._systems_archetype_name = None self._grounds = [] self._roofs = [] self._walls = [] @@ -468,3 +469,19 @@ class Building(CityObject): :param value: [GenericEnergySystem] """ self._energy_systems = value + + @property + def systems_archetype_name(self): + """ + Get systems archetype name + :return: str + """ + return self._systems_archetype_name + + @systems_archetype_name.setter + def systems_archetype_name(self, value): + """ + Set systems archetype name + :param value: str + """ + self._systems_archetype_name = value diff --git a/hub/city_model_structure/level_of_detail.py b/hub/city_model_structure/level_of_detail.py index 2c8665b9..273c60a3 100644 --- a/hub/city_model_structure/level_of_detail.py +++ b/hub/city_model_structure/level_of_detail.py @@ -16,6 +16,7 @@ class LevelOfDetail: self._usage = None self._weather = None self._surface_radiation = None + self._energy_systems = None @property def geometry(self): @@ -75,7 +76,7 @@ class LevelOfDetail: """ Set the city minimal weather level of detail, 0 (yearly), 1 (monthly), 2 (hourly) """ - self._usage = value + self._weather = value @property def surface_radiation(self): @@ -91,3 +92,18 @@ class LevelOfDetail: Set the city minimal surface radiation level of detail, 0 (yearly), 1 (monthly), 2 (hourly) """ self._surface_radiation = value + + @property + def energy_systems(self): + """ + Get the city minimal energy systems level of detail, 1 or 2 + :return: int + """ + return self._energy_systems + + @energy_systems.setter + def energy_systems(self, value): + """ + Set the city minimal energy systems level of detail, 1 or 2 + """ + self._energy_systems = value diff --git a/hub/imports/construction/nrcan_physics_parameters.py b/hub/imports/construction/nrcan_physics_parameters.py index 25867dfa..d46061cc 100644 --- a/hub/imports/construction/nrcan_physics_parameters.py +++ b/hub/imports/construction/nrcan_physics_parameters.py @@ -4,7 +4,6 @@ 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 datetime import math import sys from hub.hub_logger import get_logger @@ -24,9 +23,8 @@ class NrcanPhysicsParameters: """ NrcanPhysicsParameters class """ - def __init__(self, city, base_path, divide_in_storeys=False): + def __init__(self, city, divide_in_storeys=False): self._city = city - self._path = base_path self._divide_in_storeys = divide_in_storeys self._climate_zone = ConstructionHelper.city_to_nrcan_climate_zone(city.name) @@ -37,9 +35,8 @@ class NrcanPhysicsParameters: city = self._city nrcan_catalog = ConstructionCatalogFactory('nrcan').catalog for building in city.buildings: + function = Dictionaries().hub_function_to_nrcan_construction_function[building.function] try: - function = Dictionaries().hub_function_to_nrcan_construction_function[building.function] - archetype = self._search_archetype(nrcan_catalog, function, building.year_of_construction, self._climate_zone) except KeyError: @@ -47,7 +44,8 @@ class NrcanPhysicsParameters: f'{function} [{building.function}], building year of construction: {building.year_of_construction} ' f'and climate zone {self._climate_zone}\n') sys.stderr.write(f'Building {building.name} has unknown construction archetype for building function: ' - f'{function} [{building.function}], building year of construction: {building.year_of_construction} ' + f'{function} [{building.function}], ' + f'building year of construction: {building.year_of_construction} ' f'and climate zone {self._climate_zone}\n') continue diff --git a/hub/imports/construction/nrel_physics_parameters.py b/hub/imports/construction/nrel_physics_parameters.py index 4d74e4ea..c3dcbe07 100644 --- a/hub/imports/construction/nrel_physics_parameters.py +++ b/hub/imports/construction/nrel_physics_parameters.py @@ -23,9 +23,8 @@ class NrelPhysicsParameters: NrelPhysicsParameters class """ - def __init__(self, city, base_path, divide_in_storeys=False): + def __init__(self, city, divide_in_storeys=False): self._city = city - self._path = base_path self._divide_in_storeys = divide_in_storeys self._climate_zone = ConstructionHelper.city_to_nrel_climate_zone(city.name) @@ -36,17 +35,18 @@ class NrelPhysicsParameters: city = self._city nrel_catalog = ConstructionCatalogFactory('nrel').catalog for building in city.buildings: + function = Dictionaries().hub_function_to_nrel_construction_function[building.function] try: - function = Dictionaries().hub_function_to_nrel_construction_function[building.function] archetype = self._search_archetype(nrel_catalog, function, building.year_of_construction, self._climate_zone) except KeyError: logger.error(f'Building {building.name} has unknown construction archetype for building function: ' - f'{building.function} and building year of construction: {building.year_of_construction} ' - f'and climate zone reference norm {self._climate_zone}\n') + f'{function} [{building.function}], building year of construction: {building.year_of_construction} ' + f'and climate zone {self._climate_zone}\n') sys.stderr.write(f'Building {building.name} has unknown construction archetype for building function: ' - f'{building.function} and building year of construction: {building.year_of_construction} ' - f'and climate zone reference norm {self._climate_zone}\n') + f'{function} [{building.function}], ' + f'building year of construction: {building.year_of_construction} ' + f'and climate zone {self._climate_zone}\n') continue diff --git a/hub/imports/construction_factory.py b/hub/imports/construction_factory.py index f70dc0f9..f27d05f7 100644 --- a/hub/imports/construction_factory.py +++ b/hub/imports/construction_factory.py @@ -6,7 +6,6 @@ Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ -from pathlib import Path from hub.hub_logger import get_logger from hub.helpers.utils import validate_import_export_type from hub.imports.construction.nrel_physics_parameters import NrelPhysicsParameters @@ -19,9 +18,7 @@ class ConstructionFactory: """ ConstructionFactory class """ - def __init__(self, handler, city, base_path=None): - if base_path is None: - base_path = Path(Path(__file__).parent.parent / 'data/construction') + def __init__(self, handler, city): self._handler = '_' + handler.lower().replace(' ', '_') class_funcs = validate_import_export_type(ConstructionFactory) if self._handler not in class_funcs: @@ -29,20 +26,19 @@ class ConstructionFactory: logger.error(err_msg) raise Exception(err_msg) self._city = city - self._base_path = base_path def _nrel(self): """ Enrich the city by using NREL information """ - NrelPhysicsParameters(self._city, self._base_path).enrich_buildings() + NrelPhysicsParameters(self._city).enrich_buildings() self._city.level_of_detail.construction = 2 def _nrcan(self): """ Enrich the city by using NRCAN information """ - NrcanPhysicsParameters(self._city, self._base_path).enrich_buildings() + NrcanPhysicsParameters(self._city).enrich_buildings() self._city.level_of_detail.construction = 2 def enrich(self): @@ -57,4 +53,4 @@ class ConstructionFactory: Enrich the city given to the class using the class given handler :return: None """ - NrelPhysicsParameters(self._city, self._base_path).enrich_buildings() + NrelPhysicsParameters(self._city).enrich_buildings() diff --git a/hub/imports/energy_systems/montreal_custom_energy_system_parameters.py b/hub/imports/energy_systems/montreal_custom_energy_system_parameters.py new file mode 100644 index 00000000..43cadb36 --- /dev/null +++ b/hub/imports/energy_systems/montreal_custom_energy_system_parameters.py @@ -0,0 +1,98 @@ +""" +Montreal custom energy system importer +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 get_logger +from hub.catalog_factories.energy_systems_catalog_factory import EnergySystemsCatalogFactory +from hub.city_model_structure.energy_systems.generic_energy_system import GenericEnergySystem +from hub.city_model_structure.energy_systems.generic_generation_system import GenericGenerationSystem +from hub.city_model_structure.energy_systems.generic_distribution_system import GenericDistributionSystem + +logger = get_logger() + + +class MontrealCustomEnergySystemParameters: + """ + MontrealCustomEnergySystemParameters class + """ + + def __init__(self, city): + self._city = city + + def enrich_buildings(self): + """ + Returns the city with the system parameters assigned to the buildings + :return: + """ + city = self._city + montreal_custom_catalog = EnergySystemsCatalogFactory('montreal_custom').catalog + for building in city.buildings: + archetype_name = building.systems_archetype_name + try: + archetype = self._search_archetypes(montreal_custom_catalog, archetype_name) + except KeyError: + logger.error(f'Building {building.name} has unknown usage archetype for usage: {archetype_name}') + sys.stderr.write(f'Building {building.name} has unknown usage archetype for usage: {archetype_name}') + continue + building_systems = [] + for equipment in archetype.equipments: + energy_system = GenericEnergySystem() + energy_system.demand_types = equipment.demand_types + _generation_system = GenericGenerationSystem() + archetype_generation_equipment = equipment.generation_system + # todo: calculate powers + _generation_system.heat_power = 0 + _generation_system.cooling_power = 0 + _generation_system.electricity_power = 0 + _generation_system.fuel_type = archetype_generation_equipment.fuel_type + _generation_system.source_types = archetype_generation_equipment.source_types + _generation_system.heat_efficiency = archetype_generation_equipment.heat_efficiency + _generation_system.cooling_efficiency = archetype_generation_equipment.cooling_efficiency + _generation_system.electricity_efficiency = archetype_generation_equipment.electricity_efficiency + _generation_system.source_temperature = archetype_generation_equipment.source_temperature + _generation_system.source_mass_flow = archetype_generation_equipment.source_mass_flow + if archetype_generation_equipment.storage: + # todo: calculate storage capacity in J + _generation_system.storage_capacity = 0 + archetype_auxiliary_equipment = archetype_generation_equipment.auxiliary_equipment + if archetype_auxiliary_equipment is not None: + _auxiliary_equipment = GenericGenerationSystem() + # todo: calculate powers + _auxiliary_equipment.heat_power = 0 + _auxiliary_equipment.cooling_power = 0 + _auxiliary_equipment.electricity_power = 0 + _auxiliary_equipment.fuel_type = archetype_auxiliary_equipment.fuel_type + _auxiliary_equipment.source_types = archetype_auxiliary_equipment.source_types + _auxiliary_equipment.heat_efficiency = archetype_auxiliary_equipment.heat_efficiency + _auxiliary_equipment.cooling_efficiency = archetype_auxiliary_equipment.cooling_efficiency + _auxiliary_equipment.electricity_efficiency = archetype_auxiliary_equipment.electricity_efficiency + _auxiliary_equipment.source_temperature = archetype_auxiliary_equipment.source_temperature + _auxiliary_equipment.source_mass_flow = archetype_auxiliary_equipment.source_mass_flow + _auxiliary_equipment.storage_capacity = 0 + _generation_system.auxiliary_equipment = _auxiliary_equipment + + energy_system.generation_system = _generation_system + + _distribution_system = GenericDistributionSystem() + archetype_distribution_equipment = equipment.distribution_system + _distribution_system.type = archetype_distribution_equipment.type + _distribution_system.supply_temperature = archetype_distribution_equipment.supply_temperature + _distribution_system.distribution_consumption = archetype_distribution_equipment.distribution_consumption + _distribution_system.heat_losses = archetype_distribution_equipment.heat_losses + + energy_system.distribution_system = _distribution_system + + building_systems.append(energy_system) + + @staticmethod + def _search_archetypes(catalog, name): + archetypes = catalog.entries('archetypes') + for building_archetype in archetypes: + if str(name) == str(building_archetype.name): + return building_archetype + raise KeyError('archetype not found') diff --git a/hub/imports/energy_systems_factory.py b/hub/imports/energy_systems_factory.py index 84cb4c1e..fb923300 100644 --- a/hub/imports/energy_systems_factory.py +++ b/hub/imports/energy_systems_factory.py @@ -10,6 +10,7 @@ from hub.imports.energy_systems.air_source_hp_parameters import AirSourceHeatPum from hub.imports.energy_systems.water_to_water_hp_parameters import WaterToWaterHPParameters from hub.helpers.utils import validate_import_export_type from hub.hub_logger import get_logger +from hub.imports.energy_systems.montreal_custom_energy_system_parameters import MontrealCustomEnergySystemParameters logger = get_logger() @@ -43,6 +44,13 @@ class EnergySystemsFactory: """ WaterToWaterHPParameters(self._city, self._base_path).enrich_city() + def _montreal_custom(self): + """ + Enrich the city by using montreal custom energy systems catalog information + """ + self._city.level_of_detail.energy_systems = 1 + MontrealCustomEnergySystemParameters(self._city).enrich_buildings() + def enrich(self): """ Enrich the city given to the class using the class given handler diff --git a/hub/imports/usage/comnet_usage_parameters.py b/hub/imports/usage/comnet_usage_parameters.py index 6ee24ec6..58c02d2d 100644 --- a/hub/imports/usage/comnet_usage_parameters.py +++ b/hub/imports/usage/comnet_usage_parameters.py @@ -28,9 +28,8 @@ class ComnetUsageParameters: """ ComnetUsageParameters class """ - def __init__(self, city, base_path): + def __init__(self, city): self._city = city - self._path = base_path def enrich_buildings(self): """ diff --git a/hub/imports/usage/nrcan_usage_parameters.py b/hub/imports/usage/nrcan_usage_parameters.py index 8073ca67..39563fce 100644 --- a/hub/imports/usage/nrcan_usage_parameters.py +++ b/hub/imports/usage/nrcan_usage_parameters.py @@ -25,9 +25,8 @@ class NrcanUsageParameters: """ NrcanUsageParameters class """ - def __init__(self, city, base_path): + def __init__(self, city): self._city = city - self._path = base_path def enrich_buildings(self): """ diff --git a/hub/imports/usage_factory.py b/hub/imports/usage_factory.py index 4871532c..9602166e 100644 --- a/hub/imports/usage_factory.py +++ b/hub/imports/usage_factory.py @@ -6,7 +6,7 @@ Copyright © 2022 Concordia CERC group Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ -from pathlib import Path + from hub.imports.usage.comnet_usage_parameters import ComnetUsageParameters from hub.imports.usage.nrcan_usage_parameters import NrcanUsageParameters from hub.hub_logger import get_logger @@ -19,9 +19,7 @@ class UsageFactory: """ UsageFactory class """ - def __init__(self, handler, city, base_path=None): - if base_path is None: - base_path = Path(Path(__file__).parent.parent / 'data/usage') + def __init__(self, handler, city): self._handler = '_' + handler.lower().replace(' ', '_') class_funcs = validate_import_export_type(UsageFactory) if self._handler not in class_funcs: @@ -29,21 +27,20 @@ class UsageFactory: logger.error(err_msg) raise Exception(err_msg) self._city = city - self._base_path = base_path def _comnet(self): """ Enrich the city with COMNET usage library """ self._city.level_of_detail.usage = 2 - ComnetUsageParameters(self._city, self._base_path).enrich_buildings() + ComnetUsageParameters(self._city).enrich_buildings() def _nrcan(self): """ Enrich the city with NRCAN usage library """ self._city.level_of_detail.usage = 2 - NrcanUsageParameters(self._city, self._base_path).enrich_buildings() + NrcanUsageParameters(self._city).enrich_buildings() def enrich(self): """ diff --git a/hub/unittests/test_systems_catalog.py b/hub/unittests/test_systems_catalog.py index da1f8d01..03702fbd 100644 --- a/hub/unittests/test_systems_catalog.py +++ b/hub/unittests/test_systems_catalog.py @@ -14,6 +14,9 @@ class TestSystemsCatalog(TestCase): def test_montreal_custom_catalog(self): catalog = EnergySystemsCatalogFactory('montreal_custom').catalog catalog_categories = catalog.names() + for archetype in catalog.entries('archetypes'): + for equipment in archetype.equipments: + print(equipment._equipment_id) archetypes = catalog.names('archetypes') self.assertEqual(18, len(archetypes['archetypes'])) equipments = catalog.names('equipments') From 76ab65ea0f20725970bb6cc71c83a45c45015e84 Mon Sep 17 00:00:00 2001 From: p_monsalvete Date: Tue, 2 May 2023 12:35:42 -0400 Subject: [PATCH 09/15] changed name systems to energy systems --- hub/city_model_structure/building.py | 10 +++++----- .../montreal_custom_systems.xml | 2 +- ...ontreal_custom_energy_system_parameters.py | 20 +++---------------- 3 files changed, 9 insertions(+), 23 deletions(-) diff --git a/hub/city_model_structure/building.py b/hub/city_model_structure/building.py index 4e204065..16aabf00 100644 --- a/hub/city_model_structure/building.py +++ b/hub/city_model_structure/building.py @@ -471,17 +471,17 @@ class Building(CityObject): self._energy_systems = value @property - def systems_archetype_name(self): + def energy_systems_archetype_name(self): """ - Get systems archetype name + Get energy systems archetype name :return: str """ return self._systems_archetype_name - @systems_archetype_name.setter - def systems_archetype_name(self, value): + @energy_systems_archetype_name.setter + def energy_systems_archetype_name(self, value): """ - Set systems archetype name + Set energy systems archetype name :param value: str """ self._systems_archetype_name = value diff --git a/hub/data/energy_systems/montreal_custom_systems.xml b/hub/data/energy_systems/montreal_custom_systems.xml index 65c03767..245031ab 100644 --- a/hub/data/energy_systems/montreal_custom_systems.xml +++ b/hub/data/energy_systems/montreal_custom_systems.xml @@ -9,7 +9,7 @@ 0.85 10 2 - true + false Electrical resistance water boiler diff --git a/hub/imports/energy_systems/montreal_custom_energy_system_parameters.py b/hub/imports/energy_systems/montreal_custom_energy_system_parameters.py index 43cadb36..07a14b31 100644 --- a/hub/imports/energy_systems/montreal_custom_energy_system_parameters.py +++ b/hub/imports/energy_systems/montreal_custom_energy_system_parameters.py @@ -32,7 +32,7 @@ class MontrealCustomEnergySystemParameters: city = self._city montreal_custom_catalog = EnergySystemsCatalogFactory('montreal_custom').catalog for building in city.buildings: - archetype_name = building.systems_archetype_name + archetype_name = building.energy_systems_archetype_name try: archetype = self._search_archetypes(montreal_custom_catalog, archetype_name) except KeyError: @@ -59,22 +59,8 @@ class MontrealCustomEnergySystemParameters: if archetype_generation_equipment.storage: # todo: calculate storage capacity in J _generation_system.storage_capacity = 0 - archetype_auxiliary_equipment = archetype_generation_equipment.auxiliary_equipment - if archetype_auxiliary_equipment is not None: - _auxiliary_equipment = GenericGenerationSystem() - # todo: calculate powers - _auxiliary_equipment.heat_power = 0 - _auxiliary_equipment.cooling_power = 0 - _auxiliary_equipment.electricity_power = 0 - _auxiliary_equipment.fuel_type = archetype_auxiliary_equipment.fuel_type - _auxiliary_equipment.source_types = archetype_auxiliary_equipment.source_types - _auxiliary_equipment.heat_efficiency = archetype_auxiliary_equipment.heat_efficiency - _auxiliary_equipment.cooling_efficiency = archetype_auxiliary_equipment.cooling_efficiency - _auxiliary_equipment.electricity_efficiency = archetype_auxiliary_equipment.electricity_efficiency - _auxiliary_equipment.source_temperature = archetype_auxiliary_equipment.source_temperature - _auxiliary_equipment.source_mass_flow = archetype_auxiliary_equipment.source_mass_flow - _auxiliary_equipment.storage_capacity = 0 - _generation_system.auxiliary_equipment = _auxiliary_equipment + + _generation_system.auxiliary_equipment = None energy_system.generation_system = _generation_system From 5a99f16bc97d7ea15d2f138c53ce38730e47b77a Mon Sep 17 00:00:00 2001 From: p_monsalvete Date: Wed, 3 May 2023 09:58:52 -0400 Subject: [PATCH 10/15] solved 2 todos --- hub/city_model_structure/building_demand/surface.py | 1 + .../montreal_custom_energy_system_parameters.py | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/hub/city_model_structure/building_demand/surface.py b/hub/city_model_structure/building_demand/surface.py index a8df0330..56d73d1c 100644 --- a/hub/city_model_structure/building_demand/surface.py +++ b/hub/city_model_structure/building_demand/surface.py @@ -42,6 +42,7 @@ class Surface: self._associated_thermal_boundaries = [] self._vegetation = None self._percentage_shared = None + self._pv_coverage = None @property def name(self): diff --git a/hub/imports/energy_systems/montreal_custom_energy_system_parameters.py b/hub/imports/energy_systems/montreal_custom_energy_system_parameters.py index 07a14b31..3ce85e0c 100644 --- a/hub/imports/energy_systems/montreal_custom_energy_system_parameters.py +++ b/hub/imports/energy_systems/montreal_custom_energy_system_parameters.py @@ -45,9 +45,10 @@ class MontrealCustomEnergySystemParameters: energy_system.demand_types = equipment.demand_types _generation_system = GenericGenerationSystem() archetype_generation_equipment = equipment.generation_system - # todo: calculate powers - _generation_system.heat_power = 0 - _generation_system.cooling_power = 0 + # dhw peak does not add anything to the total heat peak + _generation_system.heat_power = building.heating_peak_load + _generation_system.cooling_power = building.cooling_peak_load + # the only system that generates electricity in the montreal custom catalog is PV systems _generation_system.electricity_power = 0 _generation_system.fuel_type = archetype_generation_equipment.fuel_type _generation_system.source_types = archetype_generation_equipment.source_types From dbcab5bcb995a309842a331f54b6d5d2321693a2 Mon Sep 17 00:00:00 2001 From: p_monsalvete Date: Thu, 4 May 2023 10:39:23 -0400 Subject: [PATCH 11/15] energy systems factory test added and solved some bugs. Not working yet --- hub/city_model_structure/building.py | 117 ++++++++++++++++++ .../building_demand/surface.py | 49 +++++++- .../energy_systems/generic_energy_system.py | 4 +- .../generic_generation_system.py | 25 +++- hub/helpers/constants.py | 17 +++ ..._system_to_hub_energy_generation_system.py | 26 ++++ hub/helpers/dictionaries.py | 7 ++ ...ontreal_custom_energy_system_parameters.py | 15 ++- hub/unittests/test_systems_factory.py | 58 +++++++++ 9 files changed, 303 insertions(+), 15 deletions(-) create mode 100644 hub/helpers/data/montreal_system_to_hub_energy_generation_system.py create mode 100644 hub/unittests/test_systems_factory.py diff --git a/hub/city_model_structure/building.py b/hub/city_model_structure/building.py index 16aabf00..24243925 100644 --- a/hub/city_model_structure/building.py +++ b/hub/city_model_structure/building.py @@ -45,6 +45,12 @@ class Building(CityObject): self._lighting_electrical_demand = dict() self._appliances_electrical_demand = dict() self._domestic_hot_water_heat_demand = dict() + self._heating_consumption = dict() + self._cooling_consumption = dict() + self._lighting_electrical_consumption = dict() + self._appliances_electrical_consumption = dict() + self._domestic_hot_water_consumption = dict() + self._onsite_electrical_production = dict() self._eave_height = None self._energy_systems = None self._systems_archetype_name = None @@ -485,3 +491,114 @@ class Building(CityObject): :param value: str """ self._systems_archetype_name = value + + @property + def heating_consumption(self): + """ + Get energy consumption for heating according to the heating system installed + return: dict + """ + for heating_demand_key in self.heating: + demand = self.heating[heating_demand_key][0] + consumption_type = cte.HEATING + final_energy_consumed = self._calculate_consumption(consumption_type, demand) + self._heating_consumption[heating_demand_key] = final_energy_consumed + return self._heating_consumption + + @property + def cooling_consumption(self): + """ + Get energy consumption for cooling according to the cooling system installed + return: dict + """ + for cooling_demand_key in self.cooling: + demand = self.cooling[cooling_demand_key][0] + consumption_type = cte.COOLING + final_energy_consumed = self._calculate_consumption(consumption_type, demand) + self._cooling_consumption[cooling_demand_key] = final_energy_consumed + return self._cooling_consumption + + @property + def domestic_hot_water_consumption(self): + """ + Get energy consumption for domestic according to the domestic hot water system installed + return: dict + """ + for domestic_hot_water_demand_key in self.domestic_hot_water_heat_demand: + demand = self.domestic_hot_water_heat_demand[domestic_hot_water_demand_key][0] + consumption_type = cte.DOMESTIC_HOT_WATER + final_energy_consumed = self._calculate_consumption(consumption_type, demand) + self._domestic_hot_water_consumption[domestic_hot_water_demand_key] = final_energy_consumed + return self._domestic_hot_water_consumption + + @property + def lighting_electrical_consumption(self): + """ + Get energy consumption for lighting according to the electricity system installed + return: dict + """ + for lighting_demand_key in self.lighting_electrical_demand: + demand = self.lighting_electrical_demand[lighting_demand_key][0] + consumption_type = cte.ELECTRICITY + final_energy_consumed = self._calculate_consumption(consumption_type, demand) + self._lighting_electrical_consumption[lighting_demand_key] = final_energy_consumed + return self._lighting_electrical_consumption + + @property + def appliances_electrical_consumption(self): + """ + Get energy consumption for appliances according to the electricity system installed + return: dict + """ + for appliances_demand_key in self.appliances_electrical_demand: + demand = self.appliances_electrical_demand[appliances_demand_key][0] + consumption_type = cte.ELECTRICITY + final_energy_consumed = self._calculate_consumption(consumption_type, demand) + self._appliances_electrical_consumption[appliances_demand_key] = final_energy_consumed + return self._appliances_electrical_consumption + + def _calculate_consumption(self, consumption_type, demand): + # todo: modify when COP depends on the hour + coefficient_of_performance = 0 + for energy_system in self.energy_systems: + for demand_type in energy_system.demand_types: + if demand_type == consumption_type: + if consumption_type == cte.HEATING or consumption_type == cte.DOMESTIC_HOT_WATER: + coefficient_of_performance = energy_system.generation_system.heat_efficiency + elif consumption_type == cte.COOLING: + coefficient_of_performance = energy_system.generation_system.cooling_efficiency + elif consumption_type == cte.ELECTRICITY: + coefficient_of_performance = energy_system.generation_system.electricity_efficiency + final_energy_consumed = [] + if coefficient_of_performance == 0: + final_energy_consumed.append(0) + else: + for demand_value in demand: + final_energy_consumed.append(demand_value / coefficient_of_performance) + return final_energy_consumed + + @property + def onsite_electrical_production(self): + """ + Get total electricity produced onsite + return: dict + """ + # Add other systems whenever new ones appear + for energy_system in self.energy_systems: + if energy_system.generation_system.type == cte.PHOTOVOLTAIC: + _efficiency = energy_system.generation_system.electricity_efficiency + _temporal_cases = self.roofs[0].global_irradiance.keys() + self._onsite_electrical_production = {} + for _case in _temporal_cases: + _results = [] + for surface in self.surfaces: + for i, value in enumerate(surface.global_irradiance[_case]): + if len(_results) == 0: + _collector_production = value * _efficiency \ + * surface.perimeter_area * surface.solar_collectors_area_reduction_factor + else: + _collector_production = _results[i] * value * _efficiency \ + * surface.perimeter_area * surface.solar_collectors_area_reduction_factor + _results.append(_collector_production) + self._onsite_electrical_production[_case] = _results + return self._onsite_electrical_production diff --git a/hub/city_model_structure/building_demand/surface.py b/hub/city_model_structure/building_demand/surface.py index 56d73d1c..10e60788 100644 --- a/hub/city_model_structure/building_demand/surface.py +++ b/hub/city_model_structure/building_demand/surface.py @@ -7,6 +7,8 @@ Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concord """ from __future__ import annotations + +import math import uuid import numpy as np from typing import List, Union @@ -42,7 +44,7 @@ class Surface: self._associated_thermal_boundaries = [] self._vegetation = None self._percentage_shared = None - self._pv_coverage = None + self._solar_collectors_area_reduction_factor = None @property def name(self): @@ -135,7 +137,7 @@ class Surface: @property def azimuth(self): """ - Get surface azimuth in radians + Get surface azimuth in radians (north = 0) :return: float """ if self._azimuth is None: @@ -146,7 +148,7 @@ class Surface: @property def inclination(self): """ - Get surface inclination in radians + Get surface inclination in radians (zenith = 0, horizon = pi/2) :return: float """ if self._inclination is None: @@ -162,10 +164,12 @@ class Surface: :return: str """ if self._type is None: - grad = np.rad2deg(self.inclination) - if grad >= 170: + inclination_cos = math.cos(self.inclination) + # 170 degrees + if inclination_cos <= -0.98: self._type = 'Ground' - elif 80 <= grad <= 100: + # between 80 and 100 degrees + elif abs(inclination_cos) <= 0.17: self._type = 'Wall' else: self._type = 'Roof' @@ -347,3 +351,36 @@ class Surface: :param value: float """ self._percentage_shared = value + + @property + def solar_collectors_area_reduction_factor(self): + """ + Get factor area collector per surface area if set or calculate using Romero Rodríguez, L. et al (2017) model if not + :return: float + """ + if self._solar_collectors_area_reduction_factor is None: + _solar_collectors_area_reduction_factor = 0 + if self.type == cte.ROOF: + _protected_building_restriction = 1 + # 10 degrees range + if abs(math.sin(self.inclination)) < 0.17: + # horizontal + _construction_restriction = 0.8 + _separation_of_panels = 0.46 + _shadow_between_panels = 0.7 + else: + # pitched + _construction_restriction = 0.9 + _separation_of_panels = 0.9 + _shadow_between_panels = 1 + _solar_collectors_area_reduction_factor = _protected_building_restriction * _construction_restriction \ + * _separation_of_panels * _shadow_between_panels + return self._solar_collectors_area_reduction_factor + + @solar_collectors_area_reduction_factor.setter + def solar_collectors_area_reduction_factor(self, value): + """ + Set factor area collector per surface area + :param value: float + """ + self._solar_collectors_area_reduction_factor = value diff --git a/hub/city_model_structure/energy_systems/generic_energy_system.py b/hub/city_model_structure/energy_systems/generic_energy_system.py index 5fd2f1e5..45767ed7 100644 --- a/hub/city_model_structure/energy_systems/generic_energy_system.py +++ b/hub/city_model_structure/energy_systems/generic_energy_system.py @@ -25,7 +25,7 @@ class GenericEnergySystem: @property def demand_types(self): """ - Get demand able to cover from [heating, cooling, domestic_hot_water, electricity] + Get demand able to cover from [Heating, Cooling, Domestic Hot Water, Electricity] :return: [string] """ return self._demand_types @@ -33,7 +33,7 @@ class GenericEnergySystem: @demand_types.setter def demand_types(self, value): """ - Set demand able to cover from [heating, cooling, domestic_hot_water, electricity] + Set demand able to cover from [Heating, Cooling, Domestic Hot Water, Electricity] :param value: [string] """ self._demand_types = value diff --git a/hub/city_model_structure/energy_systems/generic_generation_system.py b/hub/city_model_structure/energy_systems/generic_generation_system.py index 72c5fb9e..1f31bae2 100644 --- a/hub/city_model_structure/energy_systems/generic_generation_system.py +++ b/hub/city_model_structure/energy_systems/generic_generation_system.py @@ -14,6 +14,7 @@ class GenericGenerationSystem: GenericGenerationSystem class """ def __init__(self): + self._type = None self._fuel_type = None self._heat_power = None self._cooling_power = None @@ -27,10 +28,26 @@ class GenericGenerationSystem: self._storage_capacity = None self._auxiliary_equipment = None + @property + def type(self): + """ + Get system type + :return: string + """ + return self._type + + @type.setter + def type(self, value): + """ + Set system type + :param value: string + """ + self._type = value + @property def fuel_type(self): """ - Get fuel_type from [renewable, gas, diesel, electricity, wood, coal] + Get fuel_type from [Renewable, Gas, Diesel, Electricity, Wood, Coal] :return: string """ return self._fuel_type @@ -38,7 +55,7 @@ class GenericGenerationSystem: @fuel_type.setter def fuel_type(self, value): """ - Set fuel_type from [renewable, gas, diesel, electricity, wood, coal] + Set fuel_type from [Renewable, Gas, Diesel, Electricity, Wood, Coal] :param value: string """ self._fuel_type = value @@ -46,7 +63,7 @@ class GenericGenerationSystem: @property def source_types(self): """ - Get source_type from [air, water, geothermal, district_heating, grid, on_site_electricity] + Get source_type from [Air, Water, Geothermal, District Heating, Grid, Onsite Electricity] :return: [string] """ return self._source_types @@ -54,7 +71,7 @@ class GenericGenerationSystem: @source_types.setter def source_types(self, value): """ - Set source_type from [air, water, geothermal, district_heating, grid, on_site_electricity] + Set source_type from [Air, Water, Geothermal, District Heating, Grid, Onsite Electricity] :param value: [string] """ self._source_types = value diff --git a/hub/helpers/constants.py b/hub/helpers/constants.py index b147e26d..43f55240 100644 --- a/hub/helpers/constants.py +++ b/hub/helpers/constants.py @@ -164,6 +164,23 @@ EQUIPMENT = 'Equipment' ACTIVITY = 'Activity' PEOPLE_ACTIVITY_LEVEL = 'People Activity Level' DOMESTIC_HOT_WATER = 'Domestic Hot Water' +HEATING = 'Heating' +COOLING = 'Cooling' +ELECTRICITY = 'Electricity' +RENEWABLE = 'Renewable' +WOOD = 'Wood' +GAS = 'Gas' +DIESEL = 'Diesel' +COAL = 'Coal' +AIR = 'Air' +WATER = 'Water' +GEOTHERMAL = 'Geothermal' +DISTRICT_HEATING_NETWORK = 'District Heating' +GRID = 'Grid' +ONSITE_ELECTRICITY = 'Onsite Electricity' +PHOTOVOLTAIC = 'Photovoltaic' +BOILER = 'Boiler' +HEAT_PUMP = 'Heat Pump' # Geometry EPSILON = 0.0000001 diff --git a/hub/helpers/data/montreal_system_to_hub_energy_generation_system.py b/hub/helpers/data/montreal_system_to_hub_energy_generation_system.py new file mode 100644 index 00000000..ba68bd47 --- /dev/null +++ b/hub/helpers/data/montreal_system_to_hub_energy_generation_system.py @@ -0,0 +1,26 @@ +""" +Dictionaries module for Montreal system to hub energy generation system +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2023 Concordia CERC group +Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +""" + +import hub.helpers.constants as cte + + +class MontrealSystemToHubEnergyGenerationSystem: + + def __init__(self): + self._dictionary = {'Fuel-fired water boiler with baseboards': cte.BOILER, + 'Electrical resistance water boiler': cte.BOILER, + 'Fuel-fired furnace and fuel boiler for acs': cte.BOILER, + 'Baseboards: hydronic with fuel boiler': cte.BOILER, + 'Electrical baseboards and electrical boiler for acs': cte.BOILER, + 'Air cooled DX with external condenser': cte.HEAT_PUMP, + 'Water cooled, water chiller': cte.HEAT_PUMP, + 'PV system': cte.PHOTOVOLTAIC + } + + @property + def dictionary(self) -> dict: + return self._dictionary diff --git a/hub/helpers/dictionaries.py b/hub/helpers/dictionaries.py index 1fc73fde..a260e920 100644 --- a/hub/helpers/dictionaries.py +++ b/hub/helpers/dictionaries.py @@ -14,6 +14,7 @@ from hub.helpers.data.hub_function_to_nrcan_construction_function import HubFunc from hub.helpers.data.hub_usage_to_comnet_usage import HubUsageToComnetUsage from hub.helpers.data.hub_usage_to_hft_usage import HubUsageToHftUsage from hub.helpers.data.hub_usage_to_nrcan_usage import HubUsageToNrcanUsage +from hub.helpers.data.montreal_system_to_hub_energy_generation_system import MontrealSystemToHubEnergyGenerationSystem class Dictionaries: @@ -91,3 +92,9 @@ class Dictionaries: """ return AlkisFunctionToHubFunction().dictionary + @property + def montreal_system_to_hub_energy_generation_system(self): + """ + Get montreal custom system names to hub energy system names, transformation dictionary + """ + return MontrealSystemToHubEnergyGenerationSystem().dictionary diff --git a/hub/imports/energy_systems/montreal_custom_energy_system_parameters.py b/hub/imports/energy_systems/montreal_custom_energy_system_parameters.py index 3ce85e0c..4d5e25c6 100644 --- a/hub/imports/energy_systems/montreal_custom_energy_system_parameters.py +++ b/hub/imports/energy_systems/montreal_custom_energy_system_parameters.py @@ -12,6 +12,7 @@ from hub.catalog_factories.energy_systems_catalog_factory import EnergySystemsCa from hub.city_model_structure.energy_systems.generic_energy_system import GenericEnergySystem from hub.city_model_structure.energy_systems.generic_generation_system import GenericGenerationSystem from hub.city_model_structure.energy_systems.generic_distribution_system import GenericDistributionSystem +from hub.helpers.dictionaries import Dictionaries logger = get_logger() @@ -32,12 +33,17 @@ class MontrealCustomEnergySystemParameters: city = self._city montreal_custom_catalog = EnergySystemsCatalogFactory('montreal_custom').catalog for building in city.buildings: - archetype_name = building.energy_systems_archetype_name + archetype_name = f'{building.energy_systems_archetype_name}_lod1.0' +# archetype_name = building.energy_systems_archetype_name try: + print(archetype_name) archetype = self._search_archetypes(montreal_custom_catalog, archetype_name) except KeyError: - logger.error(f'Building {building.name} has unknown usage archetype for usage: {archetype_name}') - sys.stderr.write(f'Building {building.name} has unknown usage archetype for usage: {archetype_name}') + print('ERROR') + logger.error(f'Building {building.name} has unknown energy system archetype for system name: {archetype_name}') + sys.stderr.write(f'Building {building.name} has unknown energy system archetype ' + f'for system name: {archetype_name}') + print('END ERROR') continue building_systems = [] for equipment in archetype.equipments: @@ -45,6 +51,9 @@ class MontrealCustomEnergySystemParameters: energy_system.demand_types = equipment.demand_types _generation_system = GenericGenerationSystem() archetype_generation_equipment = equipment.generation_system + _type = str(equipment.name).split('_')[0] + _generation_system.type = Dictionaries().montreal_system_to_hub_energy_generation_system[ + _type] # dhw peak does not add anything to the total heat peak _generation_system.heat_power = building.heating_peak_load _generation_system.cooling_power = building.cooling_peak_load diff --git a/hub/unittests/test_systems_factory.py b/hub/unittests/test_systems_factory.py new file mode 100644 index 00000000..80c1f168 --- /dev/null +++ b/hub/unittests/test_systems_factory.py @@ -0,0 +1,58 @@ +""" +TestSystemsFactory +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 pathlib import Path +from unittest import TestCase + +from hub.imports.geometry_factory import GeometryFactory +from hub.imports.construction_factory import ConstructionFactory +from hub.imports.usage_factory import UsageFactory +from hub.imports.energy_systems_factory import EnergySystemsFactory + + +class TestSystemsFactory(TestCase): + """ + TestSystemsFactory TestCase + """ + def setUp(self) -> None: + """ + Configure test environment + :return: + """ + self._city = None + self._example_path = (Path(__file__).parent / 'tests_data').resolve() + + def _get_citygml(self, file): + file_path = (self._example_path / file).resolve() + self._city = GeometryFactory('citygml', path=file_path).city + self.assertIsNotNone(self._city, 'city is none') + self.assertIsNotNone(self._city.level_of_detail.geometry, 'wrong construction level of detail') + return self._city + + def test_montreal_custom_system_factory(self): + """ + Enrich the city with the construction information and verify it + """ + file = 'one_building_in_kelowna.gml' + city = self._get_citygml(file) + for building in city.buildings: + building.energy_systems_archetype_name = 'system 1 gas' + + EnergySystemsFactory('montreal_custom', city).enrich() + for building in city.buildings: + print(building.energy_systems) + + def test_montreal_custom_system_results(self): + """ + Enrich the city with the construction information and verify it + """ + file = 'one_building_in_kelowna.gml' + city = self._get_citygml(file) + for building in city.buildings: + building.year_of_construction = 1980 + ConstructionFactory('nrcan', city).enrich() + UsageFactory('nrcan', city).enrich() From e48dec81cf431f27e93a63816da1501875b4ac55 Mon Sep 17 00:00:00 2001 From: p_monsalvete Date: Fri, 12 May 2023 09:27:29 -0400 Subject: [PATCH 12/15] added lod to the city_objects --- .../energy_systems/nrcan_catalog.py | 0 hub/city_model_structure/building.py | 76 +++----- hub/city_model_structure/city_object.py | 10 + .../generic_generation_system.py | 43 +++++ .../insel/insel_monthly_energy_balance.py | 19 +- ...l_demand_type_to_hub_energy_demand_type.py | 22 +++ hub/helpers/dictionaries.py | 9 + hub/helpers/peak_loads.py | 172 +++++++++++------- hub/imports/construction_factory.py | 4 + ...ontreal_custom_energy_system_parameters.py | 26 +-- hub/imports/energy_systems_factory.py | 10 +- hub/imports/geometry/citygml.py | 2 + hub/imports/geometry/geojson.py | 4 +- hub/imports/geometry/gpandas.py | 2 + hub/imports/geometry/obj.py | 3 + hub/imports/geometry/rhino.py | 1 + .../results/insel_monthly_energry_balance.py | 13 +- .../results/simplified_radiosity_algorithm.py | 3 + hub/imports/usage_factory.py | 8 +- hub/imports/weather/epw_weather_parameters.py | 3 + hub/unittests/test_geometry_factory.py | 5 +- hub/unittests/test_systems_factory.py | 63 ++++--- 22 files changed, 332 insertions(+), 166 deletions(-) delete mode 100644 hub/catalog_factories/energy_systems/nrcan_catalog.py create mode 100644 hub/helpers/data/montreal_demand_type_to_hub_energy_demand_type.py diff --git a/hub/catalog_factories/energy_systems/nrcan_catalog.py b/hub/catalog_factories/energy_systems/nrcan_catalog.py deleted file mode 100644 index e69de29b..00000000 diff --git a/hub/city_model_structure/building.py b/hub/city_model_structure/building.py index 05b4c374..48bf7e6f 100644 --- a/hub/city_model_structure/building.py +++ b/hub/city_model_structure/building.py @@ -13,7 +13,7 @@ import pandas as pd from hub.hub_logger import logger import hub.helpers.constants as cte -import hub.helpers.peak_loads as pl +from hub.helpers.peak_loads import PeakLoads from hub.city_model_structure.building_demand.surface import Surface from hub.city_model_structure.city_object import CityObject from hub.city_model_structure.building_demand.household import Household @@ -50,8 +50,6 @@ class Building(CityObject): self._domestic_hot_water_heat_demand = dict() self._heating_consumption = dict() self._cooling_consumption = dict() - self._lighting_electrical_consumption = dict() - self._appliances_electrical_consumption = dict() self._domestic_hot_water_consumption = dict() self._onsite_electrical_production = dict() self._eave_height = None @@ -367,31 +365,40 @@ class Building(CityObject): self._domestic_hot_water_heat_demand = value @property - def heating_peak_load(self) -> dict: + def heating_peak_load(self) -> Union[None, dict]: """ Get heating peak load in W :return: dict{DataFrame(float)} """ results = {} + # todo: needed?? + monthly_values = None if cte.HOUR in self.heating: - monthly_values = pl.peak_loads_from_hourly(self.heating[cte.HOUR][next(iter(self.heating[cte.HOUR]))].values) + monthly_values = PeakLoads().\ + peak_loads_from_hourly(self.heating[cte.HOUR][next(iter(self.heating[cte.HOUR]))].values) else: - monthly_values = pl.heating_peak_loads_from_methodology(self) + monthly_values = PeakLoads(self).heating_peak_loads_from_methodology + if monthly_values is None: + return None results[cte.MONTH] = pd.DataFrame(monthly_values, columns=['heating peak loads']) results[cte.YEAR] = pd.DataFrame([max(monthly_values)], columns=['heating peak loads']) return results @property - def cooling_peak_load(self) -> dict: + def cooling_peak_load(self) -> Union[None, dict]: """ Get cooling peak load in W :return: dict{DataFrame(float)} """ results = {} + monthly_values = None if cte.HOUR in self.cooling: - monthly_values = pl.peak_loads_from_hourly(self.cooling[cte.HOUR][next(iter(self.cooling[cte.HOUR]))]) + # todo: .values???????? Like heating + monthly_values = PeakLoads().peak_loads_from_hourly(self.cooling[cte.HOUR][next(iter(self.cooling[cte.HOUR]))]) else: - monthly_values = pl.cooling_peak_loads_from_methodology(self) + monthly_values = PeakLoads(self).cooling_peak_loads_from_methodology + if monthly_values is None: + return None results[cte.MONTH] = pd.DataFrame(monthly_values, columns=['cooling peak loads']) results[cte.YEAR] = pd.DataFrame([max(monthly_values)], columns=['cooling peak loads']) return results @@ -532,7 +539,7 @@ class Building(CityObject): return: dict """ for heating_demand_key in self.heating: - demand = self.heating[heating_demand_key][0] + demand = self.heating[heating_demand_key][cte.INSEL_MEB] consumption_type = cte.HEATING final_energy_consumed = self._calculate_consumption(consumption_type, demand) self._heating_consumption[heating_demand_key] = final_energy_consumed @@ -545,7 +552,7 @@ class Building(CityObject): return: dict """ for cooling_demand_key in self.cooling: - demand = self.cooling[cooling_demand_key][0] + demand = self.cooling[cooling_demand_key][cte.INSEL_MEB] consumption_type = cte.COOLING final_energy_consumed = self._calculate_consumption(consumption_type, demand) self._cooling_consumption[cooling_demand_key] = final_energy_consumed @@ -558,44 +565,18 @@ class Building(CityObject): return: dict """ for domestic_hot_water_demand_key in self.domestic_hot_water_heat_demand: - demand = self.domestic_hot_water_heat_demand[domestic_hot_water_demand_key][0] + demand = self.domestic_hot_water_heat_demand[domestic_hot_water_demand_key][cte.INSEL_MEB] consumption_type = cte.DOMESTIC_HOT_WATER final_energy_consumed = self._calculate_consumption(consumption_type, demand) self._domestic_hot_water_consumption[domestic_hot_water_demand_key] = final_energy_consumed return self._domestic_hot_water_consumption - @property - def lighting_electrical_consumption(self): - """ - Get energy consumption for lighting according to the electricity system installed - return: dict - """ - for lighting_demand_key in self.lighting_electrical_demand: - demand = self.lighting_electrical_demand[lighting_demand_key][0] - consumption_type = cte.ELECTRICITY - final_energy_consumed = self._calculate_consumption(consumption_type, demand) - self._lighting_electrical_consumption[lighting_demand_key] = final_energy_consumed - return self._lighting_electrical_consumption - - @property - def appliances_electrical_consumption(self): - """ - Get energy consumption for appliances according to the electricity system installed - return: dict - """ - for appliances_demand_key in self.appliances_electrical_demand: - demand = self.appliances_electrical_demand[appliances_demand_key][0] - consumption_type = cte.ELECTRICITY - final_energy_consumed = self._calculate_consumption(consumption_type, demand) - self._appliances_electrical_consumption[appliances_demand_key] = final_energy_consumed - return self._appliances_electrical_consumption - def _calculate_consumption(self, consumption_type, demand): # todo: modify when COP depends on the hour coefficient_of_performance = 0 for energy_system in self.energy_systems: for demand_type in energy_system.demand_types: - if demand_type == consumption_type: + if demand_type.lower() == consumption_type.lower(): if consumption_type == cte.HEATING or consumption_type == cte.DOMESTIC_HOT_WATER: coefficient_of_performance = energy_system.generation_system.heat_efficiency elif consumption_type == cte.COOLING: @@ -620,18 +601,11 @@ class Building(CityObject): for energy_system in self.energy_systems: if energy_system.generation_system.type == cte.PHOTOVOLTAIC: _efficiency = energy_system.generation_system.electricity_efficiency - _temporal_cases = self.roofs[0].global_irradiance.keys() self._onsite_electrical_production = {} - for _case in _temporal_cases: - _results = [] + for _key in self.roofs[0].global_irradiance.keys(): + _results = [0 for _ in range(0, len(self.roofs[0].global_irradiance[_key]))] for surface in self.surfaces: - for i, value in enumerate(surface.global_irradiance[_case]): - if len(_results) == 0: - _collector_production = value * _efficiency \ - * surface.perimeter_area * surface.solar_collectors_area_reduction_factor - else: - _collector_production = _results[i] * value * _efficiency \ - * surface.perimeter_area * surface.solar_collectors_area_reduction_factor - _results.append(_collector_production) - self._onsite_electrical_production[_case] = _results + _results = [x + y * _efficiency * surface.perimeter_area * surface.solar_collectors_area_reduction_factor + for x, y in zip(_results, surface.global_irradiance[_key])] + self._onsite_electrical_production[_key] = _results return self._onsite_electrical_production diff --git a/hub/city_model_structure/city_object.py b/hub/city_model_structure/city_object.py index 3e688c26..c8312d9a 100644 --- a/hub/city_model_structure/city_object.py +++ b/hub/city_model_structure/city_object.py @@ -8,6 +8,7 @@ Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca from __future__ import annotations from typing import List, Union +from hub.city_model_structure.level_of_detail import LevelOfDetail from hub.city_model_structure.iot.sensor import Sensor from hub.city_model_structure.building_demand.surface import Surface from hub.city_model_structure.attributes.polyhedron import Polyhedron @@ -21,6 +22,7 @@ class CityObject: """ def __init__(self, name, surfaces): self._name = name + self._level_of_detail = LevelOfDetail() self._surfaces = surfaces self._type = None self._city_object_lower_corner = None @@ -43,6 +45,14 @@ class CityObject: self._sensors = [] self._neighbours = None + @property + def level_of_detail(self) -> LevelOfDetail: + """ + Get level of detail of different aspects of the city: geometry, construction and usage + :return: LevelOfDetail + """ + return self._level_of_detail + @property def name(self): """ diff --git a/hub/city_model_structure/energy_systems/generic_generation_system.py b/hub/city_model_structure/energy_systems/generic_generation_system.py index 1f31bae2..2ddd686b 100644 --- a/hub/city_model_structure/energy_systems/generic_generation_system.py +++ b/hub/city_model_structure/energy_systems/generic_generation_system.py @@ -26,7 +26,9 @@ class GenericGenerationSystem: self._source_temperature = None self._source_mass_flow = None self._storage_capacity = None + self._storage = None self._auxiliary_equipment = None + self._peak_coverages = None @property def type(self): @@ -82,6 +84,15 @@ class GenericGenerationSystem: Get heat_power in W :return: float """ + if building.heating_peak_load is not None: + _generation_system.heat_power = building.heating_peak_load + if building.cooling_peak_load is not None: + _generation_system.cooling_power = building.cooling_peak_load + # the only system that generates electricity in the montreal custom catalog is PV systems + _generation_system.electricity_power = 0 + if archetype_generation_equipment.storage: + # todo: calculate storage capacity in J + _generation_system.storage_capacity = 0 return self._heat_power @heat_power.setter @@ -220,6 +231,22 @@ class GenericGenerationSystem: """ self._storage_capacity = value + @property + def storage(self): + """ + Get boolean storage exists + :return: bool + """ + return self._storage + + @storage.setter + def storage(self, value): + """ + Set boolean storage exists + :return: bool + """ + self._storage = value + @property def auxiliary_equipment(self) -> Union[None, GenericGenerationSystem]: """ @@ -235,3 +262,19 @@ class GenericGenerationSystem: :param value: GenerationSystem """ self._auxiliary_equipment = value + + @property + def peak_coverages(self) -> dict: + """ + Get ratio of each energy type power peak covered by the system + :return: dict {Heating: value, Cooling: value, Domestic Hot Water: value, Electricity: value} + """ + return self._peak_coverages + + @peak_coverages.setter + def peak_coverages(self, value): + """ + Set ratio of each energy type power peak covered by the system + :param value: dict + """ + self._peak_coverages = value diff --git a/hub/exports/building_energy/insel/insel_monthly_energy_balance.py b/hub/exports/building_energy/insel/insel_monthly_energy_balance.py index 28a3b9e3..6cd228c5 100644 --- a/hub/exports/building_energy/insel/insel_monthly_energy_balance.py +++ b/hub/exports/building_energy/insel/insel_monthly_energy_balance.py @@ -51,7 +51,6 @@ class InselMonthlyEnergyBalance(Insel): ) self._export() - def _export(self): for i_file, content in enumerate(self._contents): file_name = self._insel_files_paths[i_file] @@ -63,7 +62,7 @@ class InselMonthlyEnergyBalance(Insel): levels_of_detail = self._city.level_of_detail if levels_of_detail.geometry is None: raise Exception(f'Level of detail of geometry not assigned') - if levels_of_detail.geometry < 0.5: + if levels_of_detail.geometry < 1: raise Exception(f'Level of detail of geometry = {levels_of_detail.geometry}. Required minimum level 0.5') if levels_of_detail.construction is None: raise Exception(f'Level of detail of construction not assigned') @@ -73,13 +72,15 @@ class InselMonthlyEnergyBalance(Insel): raise Exception(f'Level of detail of usage not assigned') if levels_of_detail.usage < 1: raise Exception(f'Level of detail of usage = {levels_of_detail.usage}. Required minimum level 1') - for building in self._city.buildings: - if cte.MONTH not in building.external_temperature: - raise Exception(f'Building {building.name} does not have external temperature assigned') - for surface in building.surfaces: - if surface.type != cte.GROUND: - if cte.MONTH not in surface.global_irradiance: - raise Exception(f'Building {building.name} does not have global irradiance on surfaces assigned') + if levels_of_detail.weather is None: + raise Exception(f'Level of detail of usage not assigned') + if levels_of_detail.weather < 1: + raise Exception(f'Level of detail of weather = {levels_of_detail.weather}. Required minimum level 1') + if levels_of_detail.surface_radiation is None: + raise Exception(f'Level of detail of usage not assigned') + if levels_of_detail.surface_radiation < 1: + raise Exception(f'Level of detail of surface radiation = {levels_of_detail.surface_radiation}. ' + f'Required minimum level 1') @staticmethod def _generate_meb_template(building, insel_outputs_path, radiation_calculation_method, weather_format): diff --git a/hub/helpers/data/montreal_demand_type_to_hub_energy_demand_type.py b/hub/helpers/data/montreal_demand_type_to_hub_energy_demand_type.py new file mode 100644 index 00000000..26d0d454 --- /dev/null +++ b/hub/helpers/data/montreal_demand_type_to_hub_energy_demand_type.py @@ -0,0 +1,22 @@ +""" +Dictionaries module for Montreal system catalog demand types to hub energy demand types +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2023 Concordia CERC group +Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +""" + +import hub.helpers.constants as cte + + +class MontrealDemandTypeToHubEnergyDemandType: + + def __init__(self): + self._dictionary = {'heating': cte.HEATING, + 'cooling': cte.COOLING, + 'domestic_hot_water': cte.DOMESTIC_HOT_WATER, + 'electricity': cte.ELECTRICITY, + } + + @property + def dictionary(self) -> dict: + return self._dictionary diff --git a/hub/helpers/dictionaries.py b/hub/helpers/dictionaries.py index 7cb94d79..c596857c 100644 --- a/hub/helpers/dictionaries.py +++ b/hub/helpers/dictionaries.py @@ -15,6 +15,7 @@ from hub.helpers.data.hub_usage_to_comnet_usage import HubUsageToComnetUsage from hub.helpers.data.hub_usage_to_hft_usage import HubUsageToHftUsage from hub.helpers.data.hub_usage_to_nrcan_usage import HubUsageToNrcanUsage from hub.helpers.data.montreal_system_to_hub_energy_generation_system import MontrealSystemToHubEnergyGenerationSystem +from hub.helpers.data.montreal_demand_type_to_hub_energy_demand_type import MontrealDemandTypeToHubEnergyDemandType from hub.helpers.data.hub_function_to_montreal_custom_costs_function import HubFunctionToMontrealCustomCostsFunction @@ -99,6 +100,14 @@ class Dictionaries: Get montreal custom system names to hub energy system names, transformation dictionary """ return MontrealSystemToHubEnergyGenerationSystem().dictionary + + @property + def montreal_demand_type_to_hub_energy_demand_type(self): + """ + Get montreal custom system demand type to hub energy demand type, transformation dictionary + """ + return MontrealDemandTypeToHubEnergyDemandType().dictionary + @property def hub_function_to_montreal_custom_costs_function(self) -> dict: """ diff --git a/hub/helpers/peak_loads.py b/hub/helpers/peak_loads.py index 11bfc208..a9bcf58f 100644 --- a/hub/helpers/peak_loads.py +++ b/hub/helpers/peak_loads.py @@ -1,71 +1,121 @@ +""" +Cooling and Heating peak loads module +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2023 Concordia CERC group +Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +Code contributors: Guille Gutierrez guillermo.gutierrezmorote@concordia.ca +""" + +import sys import math +from hub.hub_logger import logger import hub.helpers.constants as cte from hub.helpers.peak_calculation.loads_calculation import LoadsCalculation _MONTH_STARTING_HOUR = [0, 744, 1416, 2160, 2880, 3624, 4344, 5088, 5832, 6552, 7296, 8016, math.inf] -def peak_loads_from_hourly(hourly_values): - month = 1 - peaks = [0 for _ in range(12)] - for i, value in enumerate(hourly_values): - if _MONTH_STARTING_HOUR[month] <= i: - month += 1 - if value > peaks[month-1]: - peaks[month-1] = value - return peaks -def heating_peak_loads_from_methodology(building): - monthly_heating_loads = [] - ambient_temperature = building.external_temperature[cte.HOUR]['epw'] - for month in range(0, 12): - ground_temperature = building.ground_temperature[cte.MONTH]['2'][month] - heating_ambient_temperature = 100 - start_hour = _MONTH_STARTING_HOUR[month] - end_hour = 8760 - if month < 11: - end_hour = _MONTH_STARTING_HOUR[month + 1] - for hour in range(start_hour, end_hour): - temperature = ambient_temperature[hour] - if temperature < heating_ambient_temperature: - heating_ambient_temperature = temperature - loads = LoadsCalculation(building) - heating_load_transmitted = loads.get_heating_transmitted_load(heating_ambient_temperature, ground_temperature) - heating_load_ventilation_sensible = loads.get_heating_ventilation_load_sensible(heating_ambient_temperature) - heating_load_ventilation_latent = 0 - heating_load = heating_load_transmitted + heating_load_ventilation_sensible + heating_load_ventilation_latent - if heating_load < 0: - heating_load = 0 - monthly_heating_loads.append(heating_load) - return monthly_heating_loads +class PeakLoads: + """ + PeakLoads class + """ + def __init__(self, building=None): + self._building = building -def cooling_peak_loads_from_methodology(building): - monthly_cooling_loads = [] - ambient_temperature = building.external_temperature[cte.HOUR]['epw'] - for month in range(0, 12): - ground_temperature = building.ground_temperature[cte.MONTH]['2'][month] - cooling_ambient_temperature = -100 - cooling_calculation_hour = -1 - start_hour = _MONTH_STARTING_HOUR[month] - end_hour = 8760 - if month < 11: - end_hour = _MONTH_STARTING_HOUR[month + 1] - for hour in range(start_hour, end_hour): - temperature = ambient_temperature[hour] - if temperature > cooling_ambient_temperature: - cooling_ambient_temperature = temperature - cooling_calculation_hour = hour - loads = LoadsCalculation(building) - cooling_load_transmitted = loads.get_cooling_transmitted_load(cooling_ambient_temperature, ground_temperature) - cooling_load_renovation_sensible = loads.get_cooling_ventilation_load_sensible(cooling_ambient_temperature) - cooling_load_internal_gains_sensible = loads.get_internal_load_sensible() - cooling_load_radiation = loads.get_radiation_load('sra', cooling_calculation_hour) - cooling_load_sensible = cooling_load_transmitted + cooling_load_renovation_sensible - cooling_load_radiation \ - - cooling_load_internal_gains_sensible + def _can_be_calculated(self): + levels_of_detail = self._building.level_of_detail + can_be_calculated = True + if levels_of_detail.geometry is None: + can_be_calculated = False + if levels_of_detail.geometry < 1: + can_be_calculated = False + if levels_of_detail.construction is None: + can_be_calculated = False + if levels_of_detail.construction < 1: + can_be_calculated = False + if levels_of_detail.usage is None: + can_be_calculated = False + if levels_of_detail.usage < 1: + can_be_calculated = False + if levels_of_detail.weather is None: + can_be_calculated = False + if levels_of_detail.weather < 2: + can_be_calculated = False + if levels_of_detail.surface_radiation is None: + can_be_calculated = False + if levels_of_detail.surface_radiation < 2: + can_be_calculated = False + return can_be_calculated - cooling_load_latent = 0 - cooling_load = cooling_load_sensible + cooling_load_latent - if cooling_load > 0: - cooling_load = 0 - monthly_cooling_loads.append(abs(cooling_load)) - return monthly_cooling_loads + @staticmethod + def peak_loads_from_hourly(hourly_values): + month = 1 + peaks = [0 for _ in range(12)] + for i, value in enumerate(hourly_values): + if _MONTH_STARTING_HOUR[month] <= i: + month += 1 + if value > peaks[month-1]: + peaks[month-1] = value + return peaks + + @property + def heating_peak_loads_from_methodology(self): + if self._can_be_calculated(): + return None + monthly_heating_loads = [] + ambient_temperature = self._building.external_temperature[cte.HOUR]['epw'] + for month in range(0, 12): + ground_temperature = self._building.ground_temperature[cte.MONTH]['2'][month] + heating_ambient_temperature = 100 + start_hour = _MONTH_STARTING_HOUR[month] + end_hour = 8760 + if month < 11: + end_hour = _MONTH_STARTING_HOUR[month + 1] + for hour in range(start_hour, end_hour): + temperature = ambient_temperature[hour] + if temperature < heating_ambient_temperature: + heating_ambient_temperature = temperature + loads = LoadsCalculation(self._building) + heating_load_transmitted = loads.get_heating_transmitted_load(heating_ambient_temperature, ground_temperature) + heating_load_ventilation_sensible = loads.get_heating_ventilation_load_sensible(heating_ambient_temperature) + heating_load_ventilation_latent = 0 + heating_load = heating_load_transmitted + heating_load_ventilation_sensible + heating_load_ventilation_latent + if heating_load < 0: + heating_load = 0 + monthly_heating_loads.append(heating_load) + return monthly_heating_loads + + @property + def cooling_peak_loads_from_methodology(self): + if self._can_be_calculated(): + return None + monthly_cooling_loads = [] + ambient_temperature = self._building.external_temperature[cte.HOUR]['epw'] + for month in range(0, 12): + ground_temperature = self._building.ground_temperature[cte.MONTH]['2'][month] + cooling_ambient_temperature = -100 + cooling_calculation_hour = -1 + start_hour = _MONTH_STARTING_HOUR[month] + end_hour = 8760 + if month < 11: + end_hour = _MONTH_STARTING_HOUR[month + 1] + for hour in range(start_hour, end_hour): + temperature = ambient_temperature[hour] + if temperature > cooling_ambient_temperature: + cooling_ambient_temperature = temperature + cooling_calculation_hour = hour + loads = LoadsCalculation(self._building) + cooling_load_transmitted = loads.get_cooling_transmitted_load(cooling_ambient_temperature, ground_temperature) + cooling_load_renovation_sensible = loads.get_cooling_ventilation_load_sensible(cooling_ambient_temperature) + cooling_load_internal_gains_sensible = loads.get_internal_load_sensible() + cooling_load_radiation = loads.get_radiation_load('sra', cooling_calculation_hour) + cooling_load_sensible = cooling_load_transmitted + cooling_load_renovation_sensible - cooling_load_radiation \ + - cooling_load_internal_gains_sensible + + cooling_load_latent = 0 + cooling_load = cooling_load_sensible + cooling_load_latent + if cooling_load > 0: + cooling_load = 0 + monthly_cooling_loads.append(abs(cooling_load)) + return monthly_cooling_loads diff --git a/hub/imports/construction_factory.py b/hub/imports/construction_factory.py index f27d05f7..6c903a17 100644 --- a/hub/imports/construction_factory.py +++ b/hub/imports/construction_factory.py @@ -33,6 +33,8 @@ class ConstructionFactory: """ NrelPhysicsParameters(self._city).enrich_buildings() self._city.level_of_detail.construction = 2 + for building in self._city.buildings: + building.level_of_detail.construction = 2 def _nrcan(self): """ @@ -40,6 +42,8 @@ class ConstructionFactory: """ NrcanPhysicsParameters(self._city).enrich_buildings() self._city.level_of_detail.construction = 2 + for building in self._city.buildings: + building.level_of_detail.construction = 2 def enrich(self): """ diff --git a/hub/imports/energy_systems/montreal_custom_energy_system_parameters.py b/hub/imports/energy_systems/montreal_custom_energy_system_parameters.py index 4d5e25c6..11bfe59f 100644 --- a/hub/imports/energy_systems/montreal_custom_energy_system_parameters.py +++ b/hub/imports/energy_systems/montreal_custom_energy_system_parameters.py @@ -7,14 +7,13 @@ Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca import sys -from hub.hub_logger import get_logger +from hub.hub_logger import logger from hub.catalog_factories.energy_systems_catalog_factory import EnergySystemsCatalogFactory from hub.city_model_structure.energy_systems.generic_energy_system import GenericEnergySystem from hub.city_model_structure.energy_systems.generic_generation_system import GenericGenerationSystem from hub.city_model_structure.energy_systems.generic_distribution_system import GenericDistributionSystem from hub.helpers.dictionaries import Dictionaries - -logger = get_logger() +import hub.helpers.constants as cte class MontrealCustomEnergySystemParameters: @@ -48,17 +47,15 @@ class MontrealCustomEnergySystemParameters: building_systems = [] for equipment in archetype.equipments: energy_system = GenericEnergySystem() - energy_system.demand_types = equipment.demand_types + _hub_demand_types = [] + for demand_type in equipment.demand_types: + _hub_demand_types.append(Dictionaries().montreal_demand_type_to_hub_energy_demand_type[demand_type]) + energy_system.demand_types = _hub_demand_types _generation_system = GenericGenerationSystem() archetype_generation_equipment = equipment.generation_system _type = str(equipment.name).split('_')[0] _generation_system.type = Dictionaries().montreal_system_to_hub_energy_generation_system[ _type] - # dhw peak does not add anything to the total heat peak - _generation_system.heat_power = building.heating_peak_load - _generation_system.cooling_power = building.cooling_peak_load - # the only system that generates electricity in the montreal custom catalog is PV systems - _generation_system.electricity_power = 0 _generation_system.fuel_type = archetype_generation_equipment.fuel_type _generation_system.source_types = archetype_generation_equipment.source_types _generation_system.heat_efficiency = archetype_generation_equipment.heat_efficiency @@ -66,10 +63,12 @@ class MontrealCustomEnergySystemParameters: _generation_system.electricity_efficiency = archetype_generation_equipment.electricity_efficiency _generation_system.source_temperature = archetype_generation_equipment.source_temperature _generation_system.source_mass_flow = archetype_generation_equipment.source_mass_flow - if archetype_generation_equipment.storage: - # todo: calculate storage capacity in J - _generation_system.storage_capacity = 0 - + # dhw peak does not add anything to the total heat peak + _generation_system.peak_coverages = {cte.HEATING: 1, + cte.COOLING: 1, + cte.DOMESTIC_HOT_WATER: 0, + cte.ELECTRICITY: 0} + _generation_system.storage = archetype_generation_equipment.storage _generation_system.auxiliary_equipment = None energy_system.generation_system = _generation_system @@ -84,6 +83,7 @@ class MontrealCustomEnergySystemParameters: energy_system.distribution_system = _distribution_system building_systems.append(energy_system) + building.energy_systems = building_systems @staticmethod def _search_archetypes(catalog, name): diff --git a/hub/imports/energy_systems_factory.py b/hub/imports/energy_systems_factory.py index fb923300..b09d560c 100644 --- a/hub/imports/energy_systems_factory.py +++ b/hub/imports/energy_systems_factory.py @@ -37,19 +37,27 @@ class EnergySystemsFactory: Enrich the city by using xlsx heat pump information """ AirSourceHeatPumpParameters(self._city, self._base_path).enrich_city() + self._city.level_of_detail.energy_systems = 0 + for building in self._city.buildings: + building.level_of_detail.energy_systems = 0 def _water_to_water_hp(self): """ Enrich the city by using water to water heat pump information """ WaterToWaterHPParameters(self._city, self._base_path).enrich_city() + self._city.level_of_detail.energy_systems = 0 + for building in self._city.buildings: + building.level_of_detail.energy_systems = 0 def _montreal_custom(self): """ Enrich the city by using montreal custom energy systems catalog information """ - self._city.level_of_detail.energy_systems = 1 MontrealCustomEnergySystemParameters(self._city).enrich_buildings() + self._city.level_of_detail.energy_systems = 1 + for building in self._city.buildings: + building.level_of_detail.energy_systems = 1 def enrich(self): """ diff --git a/hub/imports/geometry/citygml.py b/hub/imports/geometry/citygml.py index ce1ef4a8..bb84178d 100644 --- a/hub/imports/geometry/citygml.py +++ b/hub/imports/geometry/citygml.py @@ -166,4 +166,6 @@ class CityGml: else: self._city.add_city_object(self._create_building(city_object)) self._city.level_of_detail.geometry = self._lod + for building in self._city.buildings: + building.level_of_detail.geometry = self._lod return self._city diff --git a/hub/imports/geometry/geojson.py b/hub/imports/geometry/geojson.py index 8c820e48..f05724c7 100644 --- a/hub/imports/geometry/geojson.py +++ b/hub/imports/geometry/geojson.py @@ -177,7 +177,7 @@ class Geojson: extrusion_height = 0 if self._extrusion_height_field is not None: extrusion_height = float(feature['properties'][self._extrusion_height_field]) - lod = 0.5 + lod = 1 year_of_construction = None if self._year_of_construction_field is not None: year_of_construction = int(feature['properties'][self._year_of_construction_field]) @@ -215,6 +215,8 @@ class Geojson: if building.floor_area >= 25: self._city.add_city_object(building) self._city.level_of_detail.geometry = lod + for building in self._city.buildings: + building.level_of_detail.geometry = lod if lod > 0: lines_information = GeometryHelper.city_mapping(self._city, plot=False) self._store_shared_percentage_to_walls(self._city, lines_information) diff --git a/hub/imports/geometry/gpandas.py b/hub/imports/geometry/gpandas.py index f1bd2815..3ae71eca 100644 --- a/hub/imports/geometry/gpandas.py +++ b/hub/imports/geometry/gpandas.py @@ -83,6 +83,8 @@ class GPandas: building = Building(name, surfaces, year_of_construction, function, terrains=None) self._city.add_city_object(building) self._city.level_of_detail.geometry = lod + for building in self._city.buildings: + building.level_of_detail.geometry = lod return self._city @staticmethod diff --git a/hub/imports/geometry/obj.py b/hub/imports/geometry/obj.py index 6bf62a33..e8fc5298 100644 --- a/hub/imports/geometry/obj.py +++ b/hub/imports/geometry/obj.py @@ -81,4 +81,7 @@ class Obj: building = Building(name, surfaces, year_of_construction, function, terrains=None) self._city.add_city_object(building) self._city.level_of_detail.geometry = lod + for building in self._city.buildings: + building.level_of_detail.geometry = lod + return self._city diff --git a/hub/imports/geometry/rhino.py b/hub/imports/geometry/rhino.py index c4bf354d..2c490886 100644 --- a/hub/imports/geometry/rhino.py +++ b/hub/imports/geometry/rhino.py @@ -133,5 +133,6 @@ class Rhino: for building in buildings: city.add_city_object(building) + building.level_of_detail.geometry = 3 city.level_of_detail.geometry = 3 return city diff --git a/hub/imports/results/insel_monthly_energry_balance.py b/hub/imports/results/insel_monthly_energry_balance.py index a2b5b322..087abd67 100644 --- a/hub/imports/results/insel_monthly_energry_balance.py +++ b/hub/imports/results/insel_monthly_energry_balance.py @@ -87,9 +87,20 @@ class InselMonthlyEnergyBalance: total_dhw_demand += total_day * cte.DAYS_A_MONTH[day_type][month] * demand domestic_hot_water_demand.append(total_dhw_demand * area) - building.domestic_hot_water_heat_demand[cte.MONTH] = pd.DataFrame(domestic_hot_water_demand, columns=[cte.INSEL_MEB]) + building.domestic_hot_water_heat_demand[cte.MONTH] = pd.DataFrame(domestic_hot_water_demand, + columns=[cte.INSEL_MEB]) + yearly_domestic_hot_water_demand = [sum(domestic_hot_water_demand)] + building.domestic_hot_water_heat_demand[cte.YEAR] = pd.DataFrame(yearly_domestic_hot_water_demand, + columns=[cte.INSEL_MEB]) building.lighting_electrical_demand[cte.MONTH] = pd.DataFrame(lighting_demand, columns=[cte.INSEL_MEB]) + yearly_lighting_electrical_demand = [sum(lighting_demand)] + building.lighting_electrical_demand[cte.YEAR] = pd.DataFrame(yearly_lighting_electrical_demand, + columns=[cte.INSEL_MEB]) building.appliances_electrical_demand[cte.MONTH] = pd.DataFrame(appliances_demand, columns=[cte.INSEL_MEB]) + yearly_appliances_electrical_demand = [sum(appliances_demand)] + building.appliances_electrical_demand[cte.YEAR] = pd.DataFrame(yearly_appliances_electrical_demand, + columns=[cte.INSEL_MEB]) + def enrich(self): for building in self._city.buildings: diff --git a/hub/imports/results/simplified_radiosity_algorithm.py b/hub/imports/results/simplified_radiosity_algorithm.py index 334a26b1..e6f01cf8 100644 --- a/hub/imports/results/simplified_radiosity_algorithm.py +++ b/hub/imports/results/simplified_radiosity_algorithm.py @@ -95,3 +95,6 @@ class SimplifiedRadiosityAlgorithm: if cte.YEAR not in surface.global_irradiance: surface.global_irradiance[cte.YEAR] = self._get_yearly_mean_values(new_value) self._city.level_of_detail.surface_radiation = 2 + for building in self._city.buildings: + building.level_of_detail.surface_radiation = 2 + diff --git a/hub/imports/usage_factory.py b/hub/imports/usage_factory.py index 9602166e..bf35f4ee 100644 --- a/hub/imports/usage_factory.py +++ b/hub/imports/usage_factory.py @@ -32,15 +32,19 @@ class UsageFactory: """ Enrich the city with COMNET usage library """ - self._city.level_of_detail.usage = 2 ComnetUsageParameters(self._city).enrich_buildings() + self._city.level_of_detail.usage = 2 + for building in self._city.buildings: + building.level_of_detail.usage = 2 def _nrcan(self): """ Enrich the city with NRCAN usage library """ - self._city.level_of_detail.usage = 2 NrcanUsageParameters(self._city).enrich_buildings() + self._city.level_of_detail.usage = 2 + for building in self._city.buildings: + building.level_of_detail.usage = 2 def enrich(self): """ diff --git a/hub/imports/weather/epw_weather_parameters.py b/hub/imports/weather/epw_weather_parameters.py index 2e3b407c..390e0683 100644 --- a/hub/imports/weather/epw_weather_parameters.py +++ b/hub/imports/weather/epw_weather_parameters.py @@ -160,3 +160,6 @@ class EpwWeatherParameters: usage.domestic_hot_water.density = density self._city.level_of_detail.weather = 2 + for building in self._city.buildings: + building.level_of_detail.weather = 2 + diff --git a/hub/unittests/test_geometry_factory.py b/hub/unittests/test_geometry_factory.py index 9fb2c3ac..7e925d5b 100644 --- a/hub/unittests/test_geometry_factory.py +++ b/hub/unittests/test_geometry_factory.py @@ -8,10 +8,7 @@ from pathlib import Path from unittest import TestCase import hub.exports.exports_factory -from hub.imports.usage_factory import UsageFactory - -from hub.exports.energy_building_exports_factory import EnergyBuildingsExportsFactory -from hub.helpers.dictionaries import MontrealFunctionToHubFunction, Dictionaries +from hub.helpers.dictionaries import MontrealFunctionToHubFunction from hub.helpers.geometry_helper import GeometryHelper from hub.imports.construction_factory import ConstructionFactory from hub.imports.geometry_factory import GeometryFactory diff --git a/hub/unittests/test_systems_factory.py b/hub/unittests/test_systems_factory.py index 80c1f168..318bba7f 100644 --- a/hub/unittests/test_systems_factory.py +++ b/hub/unittests/test_systems_factory.py @@ -5,11 +5,17 @@ Copyright © 2023 Concordia CERC group Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ +import subprocess from pathlib import Path from unittest import TestCase -from hub.imports.geometry_factory import GeometryFactory +import hub.helpers.constants as cte +from hub.exports.energy_building_exports_factory import EnergyBuildingsExportsFactory +from hub.exports.exports_factory import ExportsFactory +from hub.helpers.dictionaries import Dictionaries from hub.imports.construction_factory import ConstructionFactory +from hub.imports.geometry_factory import GeometryFactory +from hub.imports.results_factory import ResultFactory from hub.imports.usage_factory import UsageFactory from hub.imports.energy_systems_factory import EnergySystemsFactory @@ -20,39 +26,50 @@ class TestSystemsFactory(TestCase): """ def setUp(self) -> None: """ - Configure test environment - :return: + Test setup + :return: None """ - self._city = None self._example_path = (Path(__file__).parent / 'tests_data').resolve() - - def _get_citygml(self, file): - file_path = (self._example_path / file).resolve() - self._city = GeometryFactory('citygml', path=file_path).city - self.assertIsNotNone(self._city, 'city is none') - self.assertIsNotNone(self._city.level_of_detail.geometry, 'wrong construction level of detail') - return self._city + self._gml_path = (self._example_path / 'FZK_Haus_LoD_2.gml').resolve() + self._output_path = (Path(__file__).parent / 'tests_outputs').resolve() + self._city = GeometryFactory('citygml', + self._gml_path, + function_to_hub=Dictionaries().alkis_function_to_hub_function).city def test_montreal_custom_system_factory(self): """ Enrich the city with the construction information and verify it """ - file = 'one_building_in_kelowna.gml' - city = self._get_citygml(file) - for building in city.buildings: + for building in self._city.buildings: building.energy_systems_archetype_name = 'system 1 gas' - EnergySystemsFactory('montreal_custom', city).enrich() - for building in city.buildings: - print(building.energy_systems) + EnergySystemsFactory('montreal_custom', self._city).enrich() + for building in self._city.buildings: + self.assertEqual(2, len(building.energy_systems[0].demand_types)) + self.assertEqual(1, len(building.energy_systems[1].demand_types)) def test_montreal_custom_system_results(self): """ Enrich the city with the construction information and verify it """ - file = 'one_building_in_kelowna.gml' - city = self._get_citygml(file) - for building in city.buildings: - building.year_of_construction = 1980 - ConstructionFactory('nrcan', city).enrich() - UsageFactory('nrcan', city).enrich() + ConstructionFactory('nrcan', self._city).enrich() + UsageFactory('nrcan', self._city).enrich() + weather_file = (self._example_path / 'CAN_PQ_Montreal.Intl.AP.716270_CWEC.epw').resolve() + ExportsFactory('sra', self._city, self._output_path, weather_file=weather_file, weather_format='epw').export() + sra_path = (self._output_path / f'{self._city.name}_sra.xml').resolve() + subprocess.run(['sra', str(sra_path)]) + ResultFactory('sra', self._city, self._output_path).enrich() + EnergyBuildingsExportsFactory('insel_monthly_energy_balance', self._city, self._output_path).export() + for building in self._city.buildings: + insel_path = (self._output_path / f'{building.name}.insel') + subprocess.run(['insel', str(insel_path)]) + ResultFactory('insel_monthly_energy_balance', self._city, self._output_path).enrich() + + for building in self._city.buildings: + building.energy_systems_archetype_name = 'system 1 gas' + EnergySystemsFactory('montreal_custom', self._city).enrich() + + for building in self._city.buildings: + self.assertLess(0, building.heating_consumption[cte.YEAR][0]) + self.assertLess(0, building.cooling_consumption[cte.YEAR][0]) + self.assertLess(0, building.domestic_hot_water_consumption[cte.YEAR][0]) From aa08306a99c31fbb9a08c59d36869b084c5ccc5a Mon Sep 17 00:00:00 2001 From: p_monsalvete Date: Fri, 12 May 2023 09:28:24 -0400 Subject: [PATCH 13/15] added lod to the city_objects --- .../energy_systems/generic_generation_system.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/hub/city_model_structure/energy_systems/generic_generation_system.py b/hub/city_model_structure/energy_systems/generic_generation_system.py index 2ddd686b..64fa35b5 100644 --- a/hub/city_model_structure/energy_systems/generic_generation_system.py +++ b/hub/city_model_structure/energy_systems/generic_generation_system.py @@ -84,15 +84,6 @@ class GenericGenerationSystem: Get heat_power in W :return: float """ - if building.heating_peak_load is not None: - _generation_system.heat_power = building.heating_peak_load - if building.cooling_peak_load is not None: - _generation_system.cooling_power = building.cooling_peak_load - # the only system that generates electricity in the montreal custom catalog is PV systems - _generation_system.electricity_power = 0 - if archetype_generation_equipment.storage: - # todo: calculate storage capacity in J - _generation_system.storage_capacity = 0 return self._heat_power @heat_power.setter From ffae3f3a04bb5fd11fb12a61aa7079fccfebff40 Mon Sep 17 00:00:00 2001 From: p_monsalvete Date: Fri, 12 May 2023 10:20:39 -0400 Subject: [PATCH 14/15] reformatting energy systems to a class --- .../energy_systems/generic_energy_system.py | 29 +++++--- .../generic_generation_system.py | 73 ++++++++----------- 2 files changed, 49 insertions(+), 53 deletions(-) diff --git a/hub/city_model_structure/energy_systems/generic_energy_system.py b/hub/city_model_structure/energy_systems/generic_energy_system.py index 45767ed7..350e3f0d 100644 --- a/hub/city_model_structure/energy_systems/generic_energy_system.py +++ b/hub/city_model_structure/energy_systems/generic_energy_system.py @@ -5,11 +5,12 @@ Copyright © 2023 Concordia CERC group Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ -from typing import Union +from typing import Union, List from hub.city_model_structure.energy_systems.generic_generation_system import GenericGenerationSystem from hub.city_model_structure.energy_systems.generic_distribution_system import GenericDistributionSystem from hub.city_model_structure.energy_systems.generic_emission_system import GenericEmissionSystem +from hub.city_model_structure.city_object import CityObject class GenericEnergySystem: @@ -21,6 +22,7 @@ class GenericEnergySystem: self._generation_system = None self._distribution_system = None self._emission_system = None + self._connected_city_objects = None @property def demand_types(self): @@ -44,16 +46,9 @@ class GenericEnergySystem: Get generation system :return: GenerationSystem """ + self._generation_system = GenericGenerationSystem(self.connected_city_objects) return self._generation_system - @generation_system.setter - def generation_system(self, value): - """ - Set generation system - :param value: GenerationSystem - """ - self._generation_system = value - @property def distribution_system(self) -> Union[None, GenericDistributionSystem]: """ @@ -85,3 +80,19 @@ class GenericEnergySystem: :param value: EmissionSystem """ self._emission_system = value + + @property + def connected_city_objects(self) -> Union[None, List[CityObject]]: + """ + Get list of city objects that are connected to this energy system + :return: List[CityObject] + """ + return self._connected_city_objects + + @connected_city_objects.setter + def connected_city_objects(self, value): + """ + Set list of city objects that are connected to this energy system + :param value: List[CityObject] + """ + self._connected_city_objects = value diff --git a/hub/city_model_structure/energy_systems/generic_generation_system.py b/hub/city_model_structure/energy_systems/generic_generation_system.py index 64fa35b5..1d19ea5c 100644 --- a/hub/city_model_structure/energy_systems/generic_generation_system.py +++ b/hub/city_model_structure/energy_systems/generic_generation_system.py @@ -8,17 +8,20 @@ Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca from __future__ import annotations from typing import Union +import hub.helpers.constants as cte + class GenericGenerationSystem: """ GenericGenerationSystem class """ - def __init__(self): + def __init__(self, city_objects=None, heat_power=None, cooling_power=None, electricity_power=None): + self._city_objects = city_objects self._type = None self._fuel_type = None - self._heat_power = None - self._cooling_power = None - self._electricity_power = None + self._heat_power = heat_power + self._cooling_power = cooling_power + self._electricity_power = electricity_power self._source_types = None self._heat_efficiency = None self._cooling_efficiency = None @@ -84,48 +87,46 @@ class GenericGenerationSystem: Get heat_power in W :return: float """ + if self._heat_power is None: + self._heat_power = 0 + for city_object in self._city_objects: + if city_object.heating_peak_load is not None: + if self.peak_coverages[cte.HEATING] is None: + return None + self._heat_power += city_object.heating_peak_load[cte.YEAR][0] * self.peak_coverages[cte.HEATING] return self._heat_power - @heat_power.setter - def heat_power(self, value): - """ - Set heat_power in W - :param value: float - """ - self._heat_power = value - @property def cooling_power(self): """ Get cooling_power in W :return: float """ + if self._cooling_power is None: + self._cooling_power = 0 + for city_object in self._city_objects: + if city_object.cooling_peak_load is not None: + if self.peak_coverages[cte.COOLING] is None: + return None + self._cooling_power += city_object.cooling_peak_load[cte.YEAR][0] * self.peak_coverages[cte.COOLING] return self._cooling_power - @cooling_power.setter - def cooling_power(self, value): - """ - Set cooling_power in W - :param value: float - """ - self._cooling_power = value - @property def electricity_power(self): """ Get electricity_power in W :return: float """ + if self._electricity_power is None: + self._electricity_power = 0 + for city_object in self._city_objects: + if city_object.electricity_peak_load is not None: + if self.peak_coverages[cte.ELECTRICITY] is None: + return None + self._electricity_power += city_object.electricity_peak_load[cte.YEAR][0]\ + * self.peak_coverages[cte.ELECTRICITY] return self._electricity_power - @electricity_power.setter - def electricity_power(self, value): - """ - Set electricity_power in W - :param value: float - """ - self._electricity_power = value - @property def heat_efficiency(self): """ @@ -214,14 +215,6 @@ class GenericGenerationSystem: """ return self._storage_capacity - @storage_capacity.setter - def storage_capacity(self, value): - """ - Set storage_capacity in J - :param value: float - """ - self._storage_capacity = value - @property def storage(self): """ @@ -246,14 +239,6 @@ class GenericGenerationSystem: """ return self._auxiliary_equipment - @auxiliary_equipment.setter - def auxiliary_equipment(self, value): - """ - Set auxiliary_equipment - :param value: GenerationSystem - """ - self._auxiliary_equipment = value - @property def peak_coverages(self) -> dict: """ From d8386d179d5752854f52b5b4ee8f76ae8640c60d Mon Sep 17 00:00:00 2001 From: p_monsalvete Date: Mon, 15 May 2023 11:03:54 -0400 Subject: [PATCH 15/15] energy systems importer finished energy systems workflow defined in the unittest --- hub/city_model_structure/building.py | 59 +++++---- hub/city_model_structure/city.py | 35 +++++ .../energy_systems/control_system.py | 30 +++++ .../energy_systems/distribution_system.py | 32 +++++ .../energy_systems/emission_system.py | 32 +++++ .../energy_systems/energy_system.py | 124 ++++++++++++++++++ .../energy_systems/generation_system.py | 120 +++++++++++++++++ .../energy_systems/generic_energy_system.py | 42 +++--- .../generic_generation_system.py | 82 +----------- hub/helpers/peak_loads.py | 6 +- hub/hub_logger/__init__.py | 6 +- ...ontreal_custom_energy_system_parameters.py | 28 ++-- hub/unittests/test_systems_factory.py | 41 +++++- 13 files changed, 495 insertions(+), 142 deletions(-) create mode 100644 hub/city_model_structure/energy_systems/control_system.py create mode 100644 hub/city_model_structure/energy_systems/distribution_system.py create mode 100644 hub/city_model_structure/energy_systems/emission_system.py create mode 100644 hub/city_model_structure/energy_systems/energy_system.py create mode 100644 hub/city_model_structure/energy_systems/generation_system.py diff --git a/hub/city_model_structure/building.py b/hub/city_model_structure/building.py index 48bf7e6f..04c4703a 100644 --- a/hub/city_model_structure/building.py +++ b/hub/city_model_structure/building.py @@ -19,7 +19,7 @@ from hub.city_model_structure.city_object import CityObject from hub.city_model_structure.building_demand.household import Household from hub.city_model_structure.building_demand.internal_zone import InternalZone from hub.city_model_structure.attributes.polyhedron import Polyhedron -from hub.city_model_structure.energy_systems.generic_energy_system import GenericEnergySystem +from hub.city_model_structure.energy_systems.energy_system import EnergySystem class Building(CityObject): @@ -371,8 +371,6 @@ class Building(CityObject): :return: dict{DataFrame(float)} """ results = {} - # todo: needed?? - monthly_values = None if cte.HOUR in self.heating: monthly_values = PeakLoads().\ peak_loads_from_hourly(self.heating[cte.HOUR][next(iter(self.heating[cte.HOUR]))].values) @@ -501,10 +499,10 @@ class Building(CityObject): return _usage.rstrip() @property - def energy_systems(self) -> Union[None, List[GenericEnergySystem]]: + def energy_systems(self) -> Union[None, List[EnergySystem]]: """ Get list of energy systems installed to cover the building demands - :return: [GenericEnergySystem] + :return: [EnergySystem] """ return self._energy_systems @@ -512,7 +510,7 @@ class Building(CityObject): def energy_systems(self, value): """ Set list of energy systems installed to cover the building demands - :param value: [GenericEnergySystem] + :param value: [EnergySystem] """ self._energy_systems = value @@ -538,11 +536,12 @@ class Building(CityObject): Get energy consumption for heating according to the heating system installed return: dict """ - for heating_demand_key in self.heating: - demand = self.heating[heating_demand_key][cte.INSEL_MEB] - consumption_type = cte.HEATING - final_energy_consumed = self._calculate_consumption(consumption_type, demand) - self._heating_consumption[heating_demand_key] = final_energy_consumed + if len(self._heating_consumption) == 0: + for heating_demand_key in self.heating: + demand = self.heating[heating_demand_key][cte.INSEL_MEB] + consumption_type = cte.HEATING + final_energy_consumed = self._calculate_consumption(consumption_type, demand) + self._heating_consumption[heating_demand_key] = final_energy_consumed return self._heating_consumption @property @@ -551,11 +550,12 @@ class Building(CityObject): Get energy consumption for cooling according to the cooling system installed return: dict """ - for cooling_demand_key in self.cooling: - demand = self.cooling[cooling_demand_key][cte.INSEL_MEB] - consumption_type = cte.COOLING - final_energy_consumed = self._calculate_consumption(consumption_type, demand) - self._cooling_consumption[cooling_demand_key] = final_energy_consumed + if len(self._cooling_consumption) == 0: + for cooling_demand_key in self.cooling: + demand = self.cooling[cooling_demand_key][cte.INSEL_MEB] + consumption_type = cte.COOLING + final_energy_consumed = self._calculate_consumption(consumption_type, demand) + self._cooling_consumption[cooling_demand_key] = final_energy_consumed return self._cooling_consumption @property @@ -564,11 +564,12 @@ class Building(CityObject): Get energy consumption for domestic according to the domestic hot water system installed return: dict """ - for domestic_hot_water_demand_key in self.domestic_hot_water_heat_demand: - demand = self.domestic_hot_water_heat_demand[domestic_hot_water_demand_key][cte.INSEL_MEB] - consumption_type = cte.DOMESTIC_HOT_WATER - final_energy_consumed = self._calculate_consumption(consumption_type, demand) - self._domestic_hot_water_consumption[domestic_hot_water_demand_key] = final_energy_consumed + if len(self._domestic_hot_water_consumption) == 0: + for domestic_hot_water_demand_key in self.domestic_hot_water_heat_demand: + demand = self.domestic_hot_water_heat_demand[domestic_hot_water_demand_key][cte.INSEL_MEB] + consumption_type = cte.DOMESTIC_HOT_WATER + final_energy_consumed = self._calculate_consumption(consumption_type, demand) + self._domestic_hot_water_consumption[domestic_hot_water_demand_key] = final_energy_consumed return self._domestic_hot_water_consumption def _calculate_consumption(self, consumption_type, demand): @@ -578,15 +579,17 @@ class Building(CityObject): for demand_type in energy_system.demand_types: if demand_type.lower() == consumption_type.lower(): if consumption_type == cte.HEATING or consumption_type == cte.DOMESTIC_HOT_WATER: - coefficient_of_performance = energy_system.generation_system.heat_efficiency + coefficient_of_performance = energy_system.generation_system.generic_generation_system.heat_efficiency elif consumption_type == cte.COOLING: - coefficient_of_performance = energy_system.generation_system.cooling_efficiency + coefficient_of_performance = energy_system.generation_system.generic_generation_system.cooling_efficiency elif consumption_type == cte.ELECTRICITY: - coefficient_of_performance = energy_system.generation_system.electricity_efficiency - final_energy_consumed = [] + coefficient_of_performance = \ + energy_system.generation_system.generic_generation_system.electricity_efficiency if coefficient_of_performance == 0: - final_energy_consumed.append(0) + values = [0]*len(demand) + final_energy_consumed = values else: + final_energy_consumed = [] for demand_value in demand: final_energy_consumed.append(demand_value / coefficient_of_performance) return final_energy_consumed @@ -599,8 +602,8 @@ class Building(CityObject): """ # Add other systems whenever new ones appear for energy_system in self.energy_systems: - if energy_system.generation_system.type == cte.PHOTOVOLTAIC: - _efficiency = energy_system.generation_system.electricity_efficiency + if energy_system.generation_system.generic_generation_system.type == cte.PHOTOVOLTAIC: + _efficiency = energy_system.generation_system.generic_generation_system.electricity_efficiency self._onsite_electrical_production = {} for _key in self.roofs[0].global_irradiance.keys(): _results = [0 for _ in range(0, len(self.roofs[0].global_irradiance[_key]))] diff --git a/hub/city_model_structure/city.py b/hub/city_model_structure/city.py index e8a34595..78d753a8 100644 --- a/hub/city_model_structure/city.py +++ b/hub/city_model_structure/city.py @@ -16,6 +16,7 @@ import pyproj from typing import List, Union from pyproj import Transformer from pathlib import Path +from pandas import DataFrame from hub.city_model_structure.building import Building from hub.city_model_structure.city_object import CityObject from hub.city_model_structure.city_objects_cluster import CityObjectsCluster @@ -61,6 +62,8 @@ class City: self._lca_materials = None self._level_of_detail = LevelOfDetail() self._city_objects_dictionary = {} + self._energy_systems_connection_table = None + self._generic_energy_systems = None @property def fuels(self) -> [Fuel]: @@ -490,4 +493,36 @@ class City: """ return self._level_of_detail + @property + def energy_systems_connection_table(self) -> Union[None, DataFrame]: + """ + Get energy systems connection table which includes at least two columns: energy_system_type and associated_building + and may also include dimensioned_energy_system and connection_building_to_dimensioned_energy_system + :return: DataFrame + """ + return self._energy_systems_connection_table + @energy_systems_connection_table.setter + def energy_systems_connection_table(self, value): + """ + Set energy systems connection table which includes at least two columns: energy_system_type and associated_building + and may also include dimensioned_energy_system and connection_building_to_dimensioned_energy_system + :param value: DataFrame + """ + self._energy_systems_connection_table = value + + @property + def generic_energy_systems(self) -> dict: + """ + Get dictionary with generic energy systems installed in the city + :return: dict + """ + return self._generic_energy_systems + + @generic_energy_systems.setter + def generic_energy_systems(self, value): + """ + Set dictionary with generic energy systems installed in the city + :return: dict + """ + self._generic_energy_systems = value diff --git a/hub/city_model_structure/energy_systems/control_system.py b/hub/city_model_structure/energy_systems/control_system.py new file mode 100644 index 00000000..0154a164 --- /dev/null +++ b/hub/city_model_structure/energy_systems/control_system.py @@ -0,0 +1,30 @@ +""" +Energy control system definition +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 ControlSystem: + """ + ControlSystem class + """ + def __init__(self): + self._control_type = None + + @property + def type(self): + """ + Get control type + :return: string + """ + return self._control_type + + @type.setter + def type(self, value): + """ + Set control type + :param value: string + """ + self._control_type = value diff --git a/hub/city_model_structure/energy_systems/distribution_system.py b/hub/city_model_structure/energy_systems/distribution_system.py new file mode 100644 index 00000000..a5a1d770 --- /dev/null +++ b/hub/city_model_structure/energy_systems/distribution_system.py @@ -0,0 +1,32 @@ +""" +Energy distribution system definition +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 hub.city_model_structure.energy_systems.generic_distribution_system import GenericDistributionSystem + + +class DistributionSystem: + """ + DistributionSystem class + """ + def __init__(self): + self._generic_distribution_system = None + + @property + def generic_distribution_system(self) -> GenericDistributionSystem: + """ + Get generic_distribution_system + :return: GenericDistributionSystem + """ + return self._generic_distribution_system + + @generic_distribution_system.setter + def generic_distribution_system(self, value): + """ + Set associated generic_distribution_system + :param value: GenericDistributionSystem + """ + self._generic_distribution_system = value diff --git a/hub/city_model_structure/energy_systems/emission_system.py b/hub/city_model_structure/energy_systems/emission_system.py new file mode 100644 index 00000000..a1fd9a30 --- /dev/null +++ b/hub/city_model_structure/energy_systems/emission_system.py @@ -0,0 +1,32 @@ +""" +Energy emission system definition +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 hub.city_model_structure.energy_systems.generic_emission_system import GenericEmissionSystem + + +class EmissionSystem: + """ + EmissionSystem class + """ + def __init__(self): + self._generic_emission_system = None + + @property + def generic_emission_system(self) -> GenericEmissionSystem: + """ + Get associated generic_emission_system + :return: GenericEmissionSystem + """ + return self._generic_emission_system + + @generic_emission_system.setter + def generic_emission_system(self, value): + """ + Set associated + :param value: GenericEmissionSystem + """ + self._generic_emission_system = value diff --git a/hub/city_model_structure/energy_systems/energy_system.py b/hub/city_model_structure/energy_systems/energy_system.py new file mode 100644 index 00000000..399566d7 --- /dev/null +++ b/hub/city_model_structure/energy_systems/energy_system.py @@ -0,0 +1,124 @@ +""" +Energy system definition +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.energy_systems.generic_energy_system import GenericEnergySystem +from hub.city_model_structure.energy_systems.generation_system import GenerationSystem +from hub.city_model_structure.energy_systems.distribution_system import DistributionSystem +from hub.city_model_structure.energy_systems.emission_system import EmissionSystem +from hub.city_model_structure.energy_systems.control_system import ControlSystem +from hub.city_model_structure.city_object import CityObject + + +class EnergySystem: + """ + EnergySystem class + """ + def __init__(self): + self._generic_energy_system = None + self._generation_system = None + self._distribution_system = None + self._emission_system = None + self._connected_city_objects = None + self._control_system = None + + @property + def generic_energy_system(self) -> GenericEnergySystem: + """ + Get associated generic_energy_system + :return: GenericEnergySystem + """ + return self._generic_energy_system + + @generic_energy_system.setter + def generic_energy_system(self, value): + """ + Set associated generic_energy_system + :param value: GenericEnergySystem + """ + self._generic_energy_system = value + + @property + def generation_system(self) -> GenerationSystem: + """ + Get generation system + :return: GenerationSystem + """ + return self._generation_system + + @generation_system.setter + def generation_system(self, value): + """ + Set generation system + :param value: GenerationSystem + """ + self._generation_system = value + + @property + def distribution_system(self) -> Union[None, DistributionSystem]: + """ + Get distribution system + :return: DistributionSystem + """ + return self._distribution_system + + @distribution_system.setter + def distribution_system(self, value): + """ + Set distribution system + :param value: DistributionSystem + """ + self._distribution_system = value + + @property + def emission_system(self) -> Union[None, EmissionSystem]: + """ + Get emission system + :return: EmissionSystem + """ + return self._emission_system + + @emission_system.setter + def emission_system(self, value): + """ + Set emission system + :param value: EmissionSystem + """ + self._emission_system = value + + @property + def connected_city_objects(self) -> Union[None, List[CityObject]]: + """ + Get list of city objects that are connected to this energy system + :return: List[CityObject] + """ + return self._connected_city_objects + + @connected_city_objects.setter + def connected_city_objects(self, value): + """ + Set list of city objects that are connected to this energy system + :param value: List[CityObject] + """ + self._connected_city_objects = value + + @property + def control_system(self) -> Union[None, ControlSystem]: + """ + Get control system of the energy system + :return: ControlSystem + """ + return self._control_system + + @control_system.setter + def control_system(self, value): + """ + Set control system of the energy system + :param value: ControlSystem + """ + self._control_system = value diff --git a/hub/city_model_structure/energy_systems/generation_system.py b/hub/city_model_structure/energy_systems/generation_system.py new file mode 100644 index 00000000..1561e370 --- /dev/null +++ b/hub/city_model_structure/energy_systems/generation_system.py @@ -0,0 +1,120 @@ +""" +Energy generation system definition +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 __future__ import annotations +from typing import Union + +from hub.city_model_structure.energy_systems.generic_generation_system import GenericGenerationSystem + + +class GenerationSystem: + """ + GenerationSystem class + """ + def __init__(self): + self._heat_power = None + self._cooling_power = None + self._electricity_power = None + self._storage_capacity = None + self._generic_generation_system = None + self._auxiliary_equipment = None + + @property + def generic_generation_system(self) -> GenericGenerationSystem: + """ + Get associated generic_generation_system + :return: GenericGenerationSystem + """ + return self._generic_generation_system + + @generic_generation_system.setter + def generic_generation_system(self, value): + """ + Set associated generic_generation_system + :param value: GenericGenerationSystem + """ + self._generic_generation_system = value + + @property + def heat_power(self): + """ + Get heat_power in W + :return: float + """ + return self._heat_power + + @heat_power.setter + def heat_power(self, value): + """ + Set heat_power in W + :param value: float + """ + self._heat_power = value + + @property + def cooling_power(self): + """ + Get cooling_power in W + :return: float + """ + return self._cooling_power + + @cooling_power.setter + def cooling_power(self, value): + """ + Set cooling_power in W + :param value: float + """ + self._cooling_power = value + + @property + def electricity_power(self): + """ + Get electricity_power in W + :return: float + """ + return self._electricity_power + + @electricity_power.setter + def electricity_power(self, value): + """ + Set electricity_power in W + :param value: float + """ + self._electricity_power = value + + @property + def storage_capacity(self): + """ + Get storage_capacity in J + :return: float + """ + return self._storage_capacity + + @storage_capacity.setter + def storage_capacity(self, value): + """ + Set storage_capacity in J + :param value: float + """ + self._storage_capacity = value + + @property + def auxiliary_equipment(self) -> Union[None, GenerationSystem]: + """ + Get auxiliary_equipment + :return: GenerationSystem + """ + return self._auxiliary_equipment + + @auxiliary_equipment.setter + def auxiliary_equipment(self, value): + """ + Set auxiliary_equipment + :param value: GenerationSystem + """ + self._auxiliary_equipment = value diff --git a/hub/city_model_structure/energy_systems/generic_energy_system.py b/hub/city_model_structure/energy_systems/generic_energy_system.py index 350e3f0d..da108a09 100644 --- a/hub/city_model_structure/energy_systems/generic_energy_system.py +++ b/hub/city_model_structure/energy_systems/generic_energy_system.py @@ -18,12 +18,29 @@ class GenericEnergySystem: GenericEnergySystem class """ def __init__(self): + self._name = None self._demand_types = None self._generation_system = None self._distribution_system = None self._emission_system = None self._connected_city_objects = None + @property + def name(self): + """ + Get energy system name + :return: str + """ + return self._name + + @name.setter + def name(self, value): + """ + Set energy system name + :param value: + """ + self._name = value + @property def demand_types(self): """ @@ -46,9 +63,16 @@ class GenericEnergySystem: Get generation system :return: GenerationSystem """ - self._generation_system = GenericGenerationSystem(self.connected_city_objects) return self._generation_system + @generation_system.setter + def generation_system(self, value): + """ + Set generation system + :return: GenerationSystem + """ + self._generation_system = value + @property def distribution_system(self) -> Union[None, GenericDistributionSystem]: """ @@ -80,19 +104,3 @@ class GenericEnergySystem: :param value: EmissionSystem """ self._emission_system = value - - @property - def connected_city_objects(self) -> Union[None, List[CityObject]]: - """ - Get list of city objects that are connected to this energy system - :return: List[CityObject] - """ - return self._connected_city_objects - - @connected_city_objects.setter - def connected_city_objects(self, value): - """ - Set list of city objects that are connected to this energy system - :param value: List[CityObject] - """ - self._connected_city_objects = value diff --git a/hub/city_model_structure/energy_systems/generic_generation_system.py b/hub/city_model_structure/energy_systems/generic_generation_system.py index 1d19ea5c..28094571 100644 --- a/hub/city_model_structure/energy_systems/generic_generation_system.py +++ b/hub/city_model_structure/energy_systems/generic_generation_system.py @@ -8,30 +8,22 @@ Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca from __future__ import annotations from typing import Union -import hub.helpers.constants as cte - class GenericGenerationSystem: """ GenericGenerationSystem class """ - def __init__(self, city_objects=None, heat_power=None, cooling_power=None, electricity_power=None): - self._city_objects = city_objects + def __init__(self): self._type = None self._fuel_type = None - self._heat_power = heat_power - self._cooling_power = cooling_power - self._electricity_power = electricity_power self._source_types = None self._heat_efficiency = None self._cooling_efficiency = None self._electricity_efficiency = None self._source_temperature = None self._source_mass_flow = None - self._storage_capacity = None self._storage = None self._auxiliary_equipment = None - self._peak_coverages = None @property def type(self): @@ -81,52 +73,6 @@ class GenericGenerationSystem: """ self._source_types = value - @property - def heat_power(self): - """ - Get heat_power in W - :return: float - """ - if self._heat_power is None: - self._heat_power = 0 - for city_object in self._city_objects: - if city_object.heating_peak_load is not None: - if self.peak_coverages[cte.HEATING] is None: - return None - self._heat_power += city_object.heating_peak_load[cte.YEAR][0] * self.peak_coverages[cte.HEATING] - return self._heat_power - - @property - def cooling_power(self): - """ - Get cooling_power in W - :return: float - """ - if self._cooling_power is None: - self._cooling_power = 0 - for city_object in self._city_objects: - if city_object.cooling_peak_load is not None: - if self.peak_coverages[cte.COOLING] is None: - return None - self._cooling_power += city_object.cooling_peak_load[cte.YEAR][0] * self.peak_coverages[cte.COOLING] - return self._cooling_power - - @property - def electricity_power(self): - """ - Get electricity_power in W - :return: float - """ - if self._electricity_power is None: - self._electricity_power = 0 - for city_object in self._city_objects: - if city_object.electricity_peak_load is not None: - if self.peak_coverages[cte.ELECTRICITY] is None: - return None - self._electricity_power += city_object.electricity_peak_load[cte.YEAR][0]\ - * self.peak_coverages[cte.ELECTRICITY] - return self._electricity_power - @property def heat_efficiency(self): """ @@ -207,14 +153,6 @@ class GenericGenerationSystem: """ self._source_mass_flow = value - @property - def storage_capacity(self): - """ - Get storage_capacity in J - :return: float - """ - return self._storage_capacity - @property def storage(self): """ @@ -239,18 +177,10 @@ class GenericGenerationSystem: """ return self._auxiliary_equipment - @property - def peak_coverages(self) -> dict: + @auxiliary_equipment.setter + def auxiliary_equipment(self, value): """ - Get ratio of each energy type power peak covered by the system - :return: dict {Heating: value, Cooling: value, Domestic Hot Water: value, Electricity: value} + Set auxiliary_equipment + :return: GenerationSystem """ - return self._peak_coverages - - @peak_coverages.setter - def peak_coverages(self, value): - """ - Set ratio of each energy type power peak covered by the system - :param value: dict - """ - self._peak_coverages = value + self._auxiliary_equipment = value diff --git a/hub/helpers/peak_loads.py b/hub/helpers/peak_loads.py index a9bcf58f..9bfb00d6 100644 --- a/hub/helpers/peak_loads.py +++ b/hub/helpers/peak_loads.py @@ -6,10 +6,8 @@ Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca Code contributors: Guille Gutierrez guillermo.gutierrezmorote@concordia.ca """ -import sys import math -from hub.hub_logger import logger import hub.helpers.constants as cte from hub.helpers.peak_calculation.loads_calculation import LoadsCalculation @@ -61,7 +59,7 @@ class PeakLoads: @property def heating_peak_loads_from_methodology(self): - if self._can_be_calculated(): + if not self._can_be_calculated(): return None monthly_heating_loads = [] ambient_temperature = self._building.external_temperature[cte.HOUR]['epw'] @@ -88,7 +86,7 @@ class PeakLoads: @property def cooling_peak_loads_from_methodology(self): - if self._can_be_calculated(): + if not self._can_be_calculated(): return None monthly_cooling_loads = [] ambient_temperature = self._building.external_temperature[cte.HOUR]['epw'] diff --git a/hub/hub_logger/__init__.py b/hub/hub_logger/__init__.py index 1c7c5f04..d1d11d35 100644 --- a/hub/hub_logger/__init__.py +++ b/hub/hub_logger/__init__.py @@ -1,7 +1,6 @@ import logging as logger from pathlib import Path import os -import logging import sys @@ -27,7 +26,6 @@ def get_logger(file_logger=False): except IOError as err: print(f'I/O exception: {err}') else: - logging.getLogger().addHandler(logging.StreamHandler(stream=sys.stdout)) - logging.getLogger().setLevel(logging.DEBUG) + logger.getLogger().addHandler(logger.StreamHandler(stream=sys.stdout)) + logger.getLogger().setLevel(logger.DEBUG) return logger.getLogger() - diff --git a/hub/imports/energy_systems/montreal_custom_energy_system_parameters.py b/hub/imports/energy_systems/montreal_custom_energy_system_parameters.py index 11bfe59f..79772efc 100644 --- a/hub/imports/energy_systems/montreal_custom_energy_system_parameters.py +++ b/hub/imports/energy_systems/montreal_custom_energy_system_parameters.py @@ -6,6 +6,7 @@ Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ import sys +from pandas import DataFrame from hub.hub_logger import logger from hub.catalog_factories.energy_systems_catalog_factory import EnergySystemsCatalogFactory @@ -13,7 +14,6 @@ from hub.city_model_structure.energy_systems.generic_energy_system import Generi from hub.city_model_structure.energy_systems.generic_generation_system import GenericGenerationSystem from hub.city_model_structure.energy_systems.generic_distribution_system import GenericDistributionSystem from hub.helpers.dictionaries import Dictionaries -import hub.helpers.constants as cte class MontrealCustomEnergySystemParameters: @@ -31,25 +31,34 @@ class MontrealCustomEnergySystemParameters: """ city = self._city montreal_custom_catalog = EnergySystemsCatalogFactory('montreal_custom').catalog + if city.energy_systems_connection_table is None: + _energy_systems_connection_table = DataFrame(columns=['Energy System Type', 'Building']) + else: + _energy_systems_connection_table = city.energy_systems_connection_table + if city.generic_energy_systems is None: + _generic_energy_systems = {} + else: + _generic_energy_systems = city.generic_energy_systems for building in city.buildings: archetype_name = f'{building.energy_systems_archetype_name}_lod1.0' # archetype_name = building.energy_systems_archetype_name try: - print(archetype_name) archetype = self._search_archetypes(montreal_custom_catalog, archetype_name) except KeyError: - print('ERROR') logger.error(f'Building {building.name} has unknown energy system archetype for system name: {archetype_name}') sys.stderr.write(f'Building {building.name} has unknown energy system archetype ' f'for system name: {archetype_name}') - print('END ERROR') continue + building_systems = [] + data = [archetype_name, building.name] + _energy_systems_connection_table.loc[len(_energy_systems_connection_table)] = data for equipment in archetype.equipments: energy_system = GenericEnergySystem() _hub_demand_types = [] for demand_type in equipment.demand_types: _hub_demand_types.append(Dictionaries().montreal_demand_type_to_hub_energy_demand_type[demand_type]) + energy_system.name = archetype_name energy_system.demand_types = _hub_demand_types _generation_system = GenericGenerationSystem() archetype_generation_equipment = equipment.generation_system @@ -63,11 +72,6 @@ class MontrealCustomEnergySystemParameters: _generation_system.electricity_efficiency = archetype_generation_equipment.electricity_efficiency _generation_system.source_temperature = archetype_generation_equipment.source_temperature _generation_system.source_mass_flow = archetype_generation_equipment.source_mass_flow - # dhw peak does not add anything to the total heat peak - _generation_system.peak_coverages = {cte.HEATING: 1, - cte.COOLING: 1, - cte.DOMESTIC_HOT_WATER: 0, - cte.ELECTRICITY: 0} _generation_system.storage = archetype_generation_equipment.storage _generation_system.auxiliary_equipment = None @@ -83,7 +87,11 @@ class MontrealCustomEnergySystemParameters: energy_system.distribution_system = _distribution_system building_systems.append(energy_system) - building.energy_systems = building_systems + if archetype_name not in _generic_energy_systems: + _generic_energy_systems[archetype_name] = building_systems + + city.energy_systems_connection_table = _energy_systems_connection_table + city.generic_energy_systems = _generic_energy_systems @staticmethod def _search_archetypes(catalog, name): diff --git a/hub/unittests/test_systems_factory.py b/hub/unittests/test_systems_factory.py index 318bba7f..4d53fa89 100644 --- a/hub/unittests/test_systems_factory.py +++ b/hub/unittests/test_systems_factory.py @@ -8,6 +8,7 @@ Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca import subprocess from pathlib import Path from unittest import TestCase +import copy import hub.helpers.constants as cte from hub.exports.energy_building_exports_factory import EnergyBuildingsExportsFactory @@ -18,6 +19,10 @@ from hub.imports.geometry_factory import GeometryFactory from hub.imports.results_factory import ResultFactory from hub.imports.usage_factory import UsageFactory from hub.imports.energy_systems_factory import EnergySystemsFactory +from hub.city_model_structure.energy_systems.energy_system import EnergySystem +from hub.city_model_structure.energy_systems.generation_system import GenerationSystem +from hub.city_model_structure.energy_systems.distribution_system import DistributionSystem +from hub.city_model_structure.energy_systems.emission_system import EmissionSystem class TestSystemsFactory(TestCase): @@ -44,9 +49,7 @@ class TestSystemsFactory(TestCase): building.energy_systems_archetype_name = 'system 1 gas' EnergySystemsFactory('montreal_custom', self._city).enrich() - for building in self._city.buildings: - self.assertEqual(2, len(building.energy_systems[0].demand_types)) - self.assertEqual(1, len(building.energy_systems[1].demand_types)) + self.assertEqual(1, len(self._city.energy_systems_connection_table)) def test_montreal_custom_system_results(self): """ @@ -64,10 +67,42 @@ class TestSystemsFactory(TestCase): insel_path = (self._output_path / f'{building.name}.insel') subprocess.run(['insel', str(insel_path)]) ResultFactory('insel_monthly_energy_balance', self._city, self._output_path).enrich() + self._city.save(self._output_path / 'city.pickle') for building in self._city.buildings: building.energy_systems_archetype_name = 'system 1 gas' EnergySystemsFactory('montreal_custom', self._city).enrich() + # Need to assign energy systems to buildings: + energy_systems_connection = self._city.energy_systems_connection_table + for building in self._city.buildings: + _building_energy_systems = [] + energy_systems = energy_systems_connection['Energy System Type']\ + .where(energy_systems_connection['Building'] == building.name) + for energy_system in energy_systems: + _generic_building_energy_systems = self._city.generic_energy_systems[energy_system] + for _generic_building_energy_system in _generic_building_energy_systems: + _building_energy_equipment = EnergySystem() + _building_energy_equipment.demand_types = _generic_building_energy_system.demand_types + + _building_distribution_system = DistributionSystem() + _building_distribution_system.generic_distribution_system = \ + copy.deepcopy(_generic_building_energy_system.distribution_system) + _building_emission_system = EmissionSystem() + _building_emission_system.generic_emission_system = \ + copy.deepcopy(_generic_building_energy_system.emission_system) + _building_generation_system = GenerationSystem() + _building_generation_system.generic_generation_system = \ + copy.deepcopy(_generic_building_energy_system.generation_system) + if cte.HEATING in _building_energy_equipment.demand_types: + _building_generation_system.heat_power = building.heating_peak_load[cte.YEAR]['heating peak loads'][0] + if cte.COOLING in _building_energy_equipment.demand_types: + _building_generation_system.cooling_power = building.cooling_peak_load[cte.YEAR]['cooling peak loads'][0] + _building_energy_equipment.generation_system = _building_generation_system + _building_energy_equipment.distribution_system = _building_distribution_system + _building_energy_equipment.emission_system = _building_emission_system + + _building_energy_systems.append(_building_energy_equipment) + building.energy_systems = _building_energy_systems for building in self._city.buildings: self.assertLess(0, building.heating_consumption[cte.YEAR][0])