Merge remote-tracking branch 'origin/systems_catalog' into retrofit_project

This commit is contained in:
Guille Gutierrez 2023-05-16 09:37:09 -04:00
commit 48dddae525
51 changed files with 2227 additions and 348 deletions

View File

@ -0,0 +1,42 @@
"""
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 typing import List
from hub.catalog_factories.data_models.energy_systems.equipment import Equipment
class Archetype:
def __init__(self, lod, name, equipments):
self._lod = lod
self._name = name
self._equipments = equipments
@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}_lod{self._lod}'
@property
def equipments(self) -> List[Equipment]:
"""
Get list of equipments that compose the total energy system
:return: [Equipment]
"""
return self._equipments

View File

@ -0,0 +1,26 @@
"""
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, equipments):
self._archetypes = archetypes
self._equipments = equipments
@property
def archetypes(self):
"""
All archetype systems in the catalog
"""
return self._archetypes
@property
def equipments(self):
"""
All equipments in the catalog
"""
return self._equipments

View File

@ -0,0 +1,48 @@
"""
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, supply_temperature, distribution_consumption,
heat_losses):
self._type = system_type
self._supply_temperature = supply_temperature
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 supply_temperature(self):
"""
Get supply_temperature in degree Celsius
:return: float
"""
return self._supply_temperature
@property
def distribution_consumption(self):
"""
Get distribution_consumption (pump of fan) in ratio over energy produced
:return: float
"""
return self._distribution_consumption
@property
def heat_losses(self):
"""
Get heat_losses in ratio over energy produced
:return: float
"""
return self._heat_losses

View File

@ -0,0 +1,28 @@
"""
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
"""
class EmissionSystem:
def __init__(self, system_type, parasitic_energy_consumption):
self._type = system_type
self._parasitic_energy_consumption = parasitic_energy_consumption
@property
def type(self):
"""
Get type
:return: string
"""
return self._type
@property
def parasitic_energy_consumption(self):
"""
Get parasitic_energy_consumption in ratio (W/W)
:return: float
"""
return self._parasitic_energy_consumption

View File

@ -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

View File

@ -0,0 +1,105 @@
"""
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
"""
from __future__ import annotations
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, auxiliary_equipment):
self._type = system_type
self._fuel_type = fuel_type
self._source_types = source_types
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 = storage
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_types(self):
"""
Get source_type from [air, water, geothermal, district_heating, grid, on_site_electricity]
:return: [string]
"""
return self._source_types
@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(self):
"""
Get boolean storage exists
:return: bool
"""
return self._storage
@property
def auxiliary_equipment(self) -> Union[None, GenerationSystem]:
"""
Get auxiliary_equipment
:return: GenerationSystem
"""
return self._auxiliary_equipment

View File

@ -0,0 +1,144 @@
"""
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.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=('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._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 = []
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: optional category filter
"""
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: optional category filter
"""
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):
"""
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.equipments:
if entry.name.lower() == name.lower():
return entry
raise IndexError(f"{name} doesn't exists in the catalog")

View File

@ -1,190 +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')
#todo: get -1 out of the setpoint
heating_setpoint_schedule_name = space_type['heating_setpoint_schedule']-1
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")

View File

@ -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:

View File

@ -13,12 +13,13 @@ 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
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.energy_system import EnergySystem
class Building(CityObject):
@ -47,7 +48,13 @@ 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._domestic_hot_water_consumption = dict()
self._onsite_electrical_production = dict()
self._eave_height = None
self._energy_systems = None
self._systems_archetype_name = None
self._grounds = []
self._roofs = []
self._walls = []
@ -193,14 +200,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):
"""
@ -366,31 +365,38 @@ 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 = {}
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
@ -491,3 +497,118 @@ 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[EnergySystem]]:
"""
Get list of energy systems installed to cover the building demands
:return: [EnergySystem]
"""
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: [EnergySystem]
"""
self._energy_systems = value
@property
def energy_systems_archetype_name(self):
"""
Get energy systems archetype name
:return: str
"""
return self._systems_archetype_name
@energy_systems_archetype_name.setter
def energy_systems_archetype_name(self, value):
"""
Set energy systems archetype name
: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
"""
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
def cooling_consumption(self):
"""
Get energy consumption for cooling according to the cooling system installed
return: dict
"""
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
def domestic_hot_water_consumption(self):
"""
Get energy consumption for domestic according to the domestic hot water system installed
return: dict
"""
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):
# 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.lower() == consumption_type.lower():
if consumption_type == cte.HEATING or consumption_type == cte.DOMESTIC_HOT_WATER:
coefficient_of_performance = energy_system.generation_system.generic_generation_system.heat_efficiency
elif consumption_type == cte.COOLING:
coefficient_of_performance = energy_system.generation_system.generic_generation_system.cooling_efficiency
elif consumption_type == cte.ELECTRICITY:
coefficient_of_performance = \
energy_system.generation_system.generic_generation_system.electricity_efficiency
if coefficient_of_performance == 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
@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.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]))]
for surface in self.surfaces:
_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

View File

@ -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,6 +44,7 @@ class Surface:
self._associated_thermal_boundaries = []
self._vegetation = None
self._percentage_shared = None
self._solar_collectors_area_reduction_factor = None
@property
def name(self):
@ -134,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:
@ -145,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:
@ -161,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'
@ -346,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

View File

@ -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

View File

@ -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):
"""

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,106 @@
"""
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, 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:
"""
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):
"""
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
:return: 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

View File

@ -0,0 +1,186 @@
"""
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._type = None
self._fuel_type = 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 = 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]
: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, Onsite 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, Onsite Electricity]
:param value: [string]
"""
self._source_types = 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(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]:
"""
Get auxiliary_equipment
:return: GenerationSystem
"""
return self._auxiliary_equipment
@auxiliary_equipment.setter
def auxiliary_equipment(self, value):
"""
Set auxiliary_equipment
:return: GenerationSystem
"""
self._auxiliary_equipment = value

View File

@ -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

View File

@ -0,0 +1,225 @@
<catalog lod="1">
<equipments>
<equipment id="1" type="boiler" fuel_type="gas">
<name>Fuel-fired water boiler with baseboards</name>
<demands>
<demand>heating</demand>
<demand>domestic_hot_water</demand>
</demands>
<heating_efficiency>0.85</heating_efficiency>
<distribution_heat_losses units="%">10</distribution_heat_losses>
<distribution_consumption units="%">2</distribution_consumption>
<storage>false</storage>
</equipment>
<equipment id="2" type="boiler" fuel_type="electricity">
<name>Electrical resistance water boiler</name>
<demands>
<demand>heating</demand>
<demand>domestic_hot_water</demand>
</demands>
<heating_efficiency>1</heating_efficiency>
<distribution_heat_losses units="%">10</distribution_heat_losses>
<distribution_consumption units="%">2</distribution_consumption>
<storage>true</storage>
</equipment>
<equipment id="3" type="furnace" fuel_type="gas">
<name>Fuel-fired furnace and fuel boiler for acs</name>
<demands>
<demand>heating</demand>
<demand>domestic_hot_water</demand>
</demands>
<heating_efficiency>0.85</heating_efficiency>
<distribution_heat_losses units="%">25</distribution_heat_losses>
<distribution_consumption units="%">100000</distribution_consumption>
<storage>false</storage>
</equipment>
<equipment id="4" type="furnace" fuel_type="electricity">
<name>Electrical resistance furnace and electrical boiler for acs</name>
<demands>
<demand>heating</demand>
<demand>domestic_hot_water</demand>
</demands>
<heating_efficiency>1</heating_efficiency>
<distribution_heat_losses units="%">25</distribution_heat_losses>
<distribution_consumption units="%">100000</distribution_consumption>
<storage>false</storage>
</equipment>
<equipment id="5" type="baseboard" fuel_type="gas">
<name>Baseboards: hydronic with fuel boiler</name>
<demands>
<demand>heating</demand>
<demand>domestic_hot_water</demand>
</demands>
<heating_efficiency>0.85</heating_efficiency>
<distribution_heat_losses units="%">10</distribution_heat_losses>
<distribution_consumption units="%">2</distribution_consumption>
<storage>false</storage>
</equipment>
<equipment id="6" type="baseboard" fuel_type="electricity">
<name>Electrical baseboards and electrical boiler for acs</name>
<demands>
<demand>heating</demand>
<demand>domestic_hot_water</demand>
</demands>
<heating_efficiency>1</heating_efficiency>
<distribution_heat_losses units="%">0</distribution_heat_losses>
<distribution_consumption units="%">0</distribution_consumption>
<storage>false</storage>
</equipment>
<equipment id="7" type="cooler" fuel_type="electricity">
<name>Air cooled DX with external condenser</name>
<demands>
<demand>cooling</demand>
</demands>
<cooling_efficiency>3.23</cooling_efficiency>
<distribution_heat_losses units="%">0</distribution_heat_losses>
<distribution_consumption units="%">100000</distribution_consumption>
<storage>false</storage>
</equipment>
<equipment id="8" type="cooler" fuel_type="electricity">
<name>Water cooled, water chiller</name>
<demands>
<demand>cooling</demand>
</demands>
<cooling_efficiency>3.23</cooling_efficiency>
<distribution_heat_losses units="%">10</distribution_heat_losses>
<distribution_consumption units="%">5</distribution_consumption>
<storage>false</storage>
</equipment>
<equipment id="9" type="cooler" fuel_type="electricity">
<name>Air cooled DX</name>
<demands>
<demand>cooling</demand>
</demands>
<cooling_efficiency>3.23</cooling_efficiency>
<distribution_heat_losses units="%">0</distribution_heat_losses>
<distribution_consumption units="%">100000</distribution_consumption>
<storage>false</storage>
</equipment>
<equipment id="10" type="electricity generator" fuel_type="renewable">
<name>PV system</name>
<demands>
<demand>electricity</demand>
</demands>
<electrical_efficiency>0.22</electrical_efficiency>
<storage>true</storage>
</equipment>
</equipments>
<systems>
<system name="system 1 gas">
<equipments>
<equipment_id>1</equipment_id>
<equipment_id>7</equipment_id>
</equipments>
</system>
<system name="system 1 gas pv">
<equipments>
<equipment_id>1</equipment_id>
<equipment_id>7</equipment_id>
<equipment_id>10</equipment_id>
</equipments>
</system>
<system name="system 1 electricity">
<equipments>
<equipment_id>2</equipment_id>
<equipment_id>7</equipment_id>
</equipments>
</system>
<system name="system 1 electricity pv">
<equipments>
<equipment_id>2</equipment_id>
<equipment_id>7</equipment_id>
<equipment_id>10</equipment_id>
</equipments>
</system>
<system name="system 2 gas">
<equipments>
<equipment_id>1</equipment_id>
<equipment_id>8</equipment_id>
</equipments>
</system>
<system name="system 2 gas pv">
<equipments>
<equipment_id>1</equipment_id>
<equipment_id>8</equipment_id>
<equipment_id>10</equipment_id>
</equipments>
</system>
<system name="system 2 electricity">
<equipments>
<equipment_id>2</equipment_id>
<equipment_id>8</equipment_id>
</equipments>
</system>
<system name="system 2 electricity pv">
<equipments>
<equipment_id>2</equipment_id>
<equipment_id>8</equipment_id>
<equipment_id>10</equipment_id>
</equipments>
</system>
<system name="system 3 and 4 gas">
<equipments>
<equipment_id>3</equipment_id>
<equipment_id>4</equipment_id>
</equipments>
</system>
<system name="system 3 and 4 gas pv">
<equipments>
<equipment_id>3</equipment_id>
<equipment_id>4</equipment_id>
<equipment_id>10</equipment_id>
</equipments>
</system>
<system name="system 3 and 4 electricity">
<equipments>
<equipment_id>4</equipment_id>
<equipment_id>4</equipment_id>
</equipments>
</system>
<system name="system 3 and 4 electricity pv">
<equipments>
<equipment_id>4</equipment_id>
<equipment_id>4</equipment_id>
<equipment_id>10</equipment_id>
</equipments>
</system>
<system name="system 5">
<equipments>
<equipment_id>8</equipment_id>
</equipments>
</system>
<system name="system 5 pv">
<equipments>
<equipment_id>8</equipment_id>
<equipment_id>10</equipment_id>
</equipments>
</system>
<system name="system 6 gas">
<equipments>
<equipment_id>5</equipment_id>
<equipment_id>8</equipment_id>
</equipments>
</system>
<system name="system 6 gas pv">
<equipments>
<equipment_id>5</equipment_id>
<equipment_id>8</equipment_id>
<equipment_id>10</equipment_id>
</equipments>
</system>
<system name="system 6 electricity">
<equipments>
<equipment_id>6</equipment_id>
<equipment_id>8</equipment_id>
</equipments>
</system>
<system name="system 6 electricity pv">
<equipments>
<equipment_id>6</equipment_id>
<equipment_id>8</equipment_id>
<equipment_id>10</equipment_id>
</equipments>
</system>
</systems>
</catalog>

View File

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<nrcan base_url="https://raw.githubusercontent.com/NREL/openstudio-standards/master/lib/openstudio-standards/standards/necb/ECMS/data/">
<boilers_location>boiler_set.json</boilers_location>
<chillers_location>chiller_set.json</chillers_location>
<curves_location>curves.json</curves_location>
<furnaces_location>furnace_set.json </furnaces_location>
<heat_pumps_location>heat_pumps.json</heat_pumps_location>
<domestic_hot_water_systems_location>shw_set.json</domestic_hot_water_systems_location>
<pvs_location>pv.json</pvs_location>
<air_conditioners_location>unitary_acs.json</air_conditioners_location>
</nrcan>

View File

@ -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):

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -14,6 +14,8 @@ 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
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
@ -92,6 +94,20 @@ 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
@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:
"""

View File

@ -1,3 +1,11 @@
"""
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 math
import hub.helpers.constants as cte
@ -5,67 +13,107 @@ 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 not 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 not 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

View File

@ -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()

View File

@ -4,9 +4,11 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later
Copyright © 2022 Concordia CERC group
Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
"""
import math
import sys
from hub.hub_logger import get_logger
from hub.hub_logger import logger
import hub.helpers.constants as cte
from hub.catalog_factories.construction_catalog_factory import ConstructionCatalogFactory
@ -16,17 +18,13 @@ from hub.helpers.dictionaries import Dictionaries
from hub.imports.construction.helpers.construction_helper import ConstructionHelper
from hub.imports.construction.helpers.storeys_generation import StoreysGeneration
logger = get_logger()
class NrcanPhysicsParameters:
"""
NrcanPhysicsParameters class
"""
def __init__(self, city, base_path, divide_in_storeys=False):
# create a thread pool with 8 threads
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)
@ -46,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
@ -68,7 +67,6 @@ class NrcanPhysicsParameters:
for internal_zone in building.internal_zones:
for thermal_zone in internal_zone.thermal_zones:
thermal_zone.total_floor_area = thermal_zone.footprint_area
for internal_zone in building.internal_zones:
self._assign_values(internal_zone.thermal_zones, archetype)
for thermal_zone in internal_zone.thermal_zones:

View File

@ -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

View File

@ -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,21 +26,24 @@ 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
for building in self._city.buildings:
building.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
for building in self._city.buildings:
building.level_of_detail.construction = 2
def enrich(self):
"""
@ -57,4 +57,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()

View File

@ -0,0 +1,102 @@
"""
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 pandas import DataFrame
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
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
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:
archetype = self._search_archetypes(montreal_custom_catalog, archetype_name)
except KeyError:
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}')
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
_type = str(equipment.name).split('_')[0]
_generation_system.type = Dictionaries().montreal_system_to_hub_energy_generation_system[
_type]
_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
_generation_system.storage = archetype_generation_equipment.storage
_generation_system.auxiliary_equipment = None
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)
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):
archetypes = catalog.entries('archetypes')
for building_archetype in archetypes:
if str(name) == str(building_archetype.name):
return building_archetype
raise KeyError('archetype not found')

View File

@ -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()
@ -36,12 +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
"""
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):
"""

View File

@ -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

View File

@ -115,7 +115,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])
@ -154,6 +154,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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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):
"""

View File

@ -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):
"""
@ -132,11 +131,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):

View File

@ -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,24 @@ 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
"""
ComnetUsageParameters(self._city).enrich_buildings()
self._city.level_of_detail.usage = 2
ComnetUsageParameters(self._city, self._base_path).enrich_buildings()
for building in self._city.buildings:
building.level_of_detail.usage = 2
def _nrcan(self):
"""
Enrich the city with NRCAN usage library
"""
NrcanUsageParameters(self._city).enrich_buildings()
self._city.level_of_detail.usage = 2
NrcanUsageParameters(self._city, self._base_path).enrich_buildings()
for building in self._city.buildings:
building.level_of_detail.usage = 2
def enrich(self):
"""

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,33 @@
"""
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()
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')
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')

View File

@ -0,0 +1,110 @@
"""
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
"""
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
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
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):
"""
TestSystemsFactory TestCase
"""
def setUp(self) -> None:
"""
Test setup
:return: None
"""
self._example_path = (Path(__file__).parent / 'tests_data').resolve()
self._gml_path = (self._example_path / 'FZK_Haus_LoD_2.gml').resolve()
self._output_path = (Path(__file__).parent / 'tests_outputs').resolve()
self._city = GeometryFactory('citygml',
self._gml_path,
function_to_hub=Dictionaries().alkis_function_to_hub_function).city
def test_montreal_custom_system_factory(self):
"""
Enrich the city with the construction information and verify it
"""
for building in self._city.buildings:
building.energy_systems_archetype_name = 'system 1 gas'
EnergySystemsFactory('montreal_custom', self._city).enrich()
self.assertEqual(1, len(self._city.energy_systems_connection_table))
def test_montreal_custom_system_results(self):
"""
Enrich the city with the construction information and verify it
"""
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()
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])
self.assertLess(0, building.cooling_consumption[cte.YEAR][0])
self.assertLess(0, building.domestic_hot_water_consumption[cte.YEAR][0])