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