diff --git a/hub/catalog_factories/cost/montreal_complete_cost_catalog.py b/hub/catalog_factories/cost/montreal_complete_cost_catalog.py index c66668ca..b41aec5d 100644 --- a/hub/catalog_factories/cost/montreal_complete_cost_catalog.py +++ b/hub/catalog_factories/cost/montreal_complete_cost_catalog.py @@ -146,16 +146,31 @@ class MontrealNewCatalog(Catalog): fuel_variable_units = item['variable']['@cost_unit'] fuel_fixed_monthly = None fuel_fixed_peak = None - if fuel_type == 'electricity': + density = None + density_unit = None + lower_heating_value = None + lower_heating_value_unit = None + if fuel_type == 'Electricity': fuel_fixed_monthly = float(item['fixed_monthly']['#text']) fuel_fixed_peak = float(item['fixed_power']['#text']) / 1000 - elif fuel_type == 'gas': - fuel_fixed_monthly = float(item['fixed_monthly']['#text']) + else: + if item['fixed_monthly']: + fuel_fixed_monthly = float(item['fixed_monthly']['#text']) + if item['density']: + density = float(item['density']['#text']) + density_unit = item['density']['@density_unit'] + if item['lower_heating_value']: + lower_heating_value = float(item['lower_heating_value']['#text']) + lower_heating_value_unit = item['lower_heating_value']['@lhv_unit'] fuel = Fuel(fuel_type, fixed_monthly=fuel_fixed_monthly, fixed_power=fuel_fixed_peak, variable=fuel_variable, - variable_units=fuel_variable_units) + variable_units=fuel_variable_units, + density=density, + density_unit=density_unit, + lower_heating_value=lower_heating_value, + lower_heating_value_unit=lower_heating_value_unit) fuels.append(fuel) heating_equipment_maintenance = float(entry['maintenance']['heating_equipment']['#text']) / 1000 cooling_equipment_maintenance = float(entry['maintenance']['cooling_equipment']['#text']) / 1000 diff --git a/hub/catalog_factories/data_models/cost/fuel.py b/hub/catalog_factories/data_models/cost/fuel.py index f785cc59..5eb4866c 100644 --- a/hub/catalog_factories/data_models/cost/fuel.py +++ b/hub/catalog_factories/data_models/cost/fuel.py @@ -16,13 +16,21 @@ class Fuel: fixed_monthly=None, fixed_power=None, variable=None, - variable_units=None): + variable_units=None, + density=None, + density_unit=None, + lower_heating_value=None, + lower_heating_value_unit=None): self._fuel_type = fuel_type self._fixed_monthly = fixed_monthly self._fixed_power = fixed_power self._variable = variable self._variable_units = variable_units + self._density = density + self._density_unit = density_unit + self._lower_heating_value = lower_heating_value + self._lower_heating_value_unit = lower_heating_value_unit @property def type(self): @@ -58,13 +66,33 @@ class Fuel: """ return self._variable, self._variable_units + @property + def density(self) -> Union[tuple[None, None], tuple[float, str]]: + """ + Get fuel density in given units + :return: None, None or float, str + """ + return self._density, self._density_unit + + @property + def lower_heating_value(self) -> Union[tuple[None, None], tuple[float, str]]: + """ + Get lower heating value in given units + :return: None, None or float, str + """ + return self._lower_heating_value, self._lower_heating_value_unit + def to_dictionary(self): """Class content to dictionary""" content = {'Fuel': {'fuel type': self.type, 'fixed operational costs [currency/month]': self.fixed_monthly, 'fixed operational costs depending on the peak power consumed [currency/month W]': self.fixed_power, 'variable operational costs': self.variable[0], - 'units': self.variable[1] + 'units': self.variable[1], + 'density': self.density[0], + 'density unit': self.density[1], + 'lower heating value': self.lower_heating_value[0], + 'lower heating value unit': self.lower_heating_value[1] } } diff --git a/hub/catalog_factories/data_models/energy_systems/__pycache__/generation_system.cpython-39.pyc b/hub/catalog_factories/data_models/energy_systems/__pycache__/generation_system.cpython-39.pyc index f24740fd..81666400 100644 Binary files a/hub/catalog_factories/data_models/energy_systems/__pycache__/generation_system.cpython-39.pyc and b/hub/catalog_factories/data_models/energy_systems/__pycache__/generation_system.cpython-39.pyc differ diff --git a/hub/catalog_factories/data_models/energy_systems/__pycache__/non_pv_generation_system.cpython-39.pyc b/hub/catalog_factories/data_models/energy_systems/__pycache__/non_pv_generation_system.cpython-39.pyc index 23f64ef9..d66f0daa 100644 Binary files a/hub/catalog_factories/data_models/energy_systems/__pycache__/non_pv_generation_system.cpython-39.pyc and b/hub/catalog_factories/data_models/energy_systems/__pycache__/non_pv_generation_system.cpython-39.pyc differ diff --git a/hub/catalog_factories/data_models/energy_systems/__pycache__/pv_generation_system.cpython-39.pyc b/hub/catalog_factories/data_models/energy_systems/__pycache__/pv_generation_system.cpython-39.pyc index 7fc22430..489baa34 100644 Binary files a/hub/catalog_factories/data_models/energy_systems/__pycache__/pv_generation_system.cpython-39.pyc and b/hub/catalog_factories/data_models/energy_systems/__pycache__/pv_generation_system.cpython-39.pyc differ diff --git a/hub/catalog_factories/data_models/energy_systems/__pycache__/thermal_storage_system.cpython-39.pyc b/hub/catalog_factories/data_models/energy_systems/__pycache__/thermal_storage_system.cpython-39.pyc index 8ee8d1d8..31ec81fc 100644 Binary files a/hub/catalog_factories/data_models/energy_systems/__pycache__/thermal_storage_system.cpython-39.pyc and b/hub/catalog_factories/data_models/energy_systems/__pycache__/thermal_storage_system.cpython-39.pyc differ diff --git a/hub/catalog_factories/data_models/energy_systems/generation_system.py b/hub/catalog_factories/data_models/energy_systems/generation_system.py index cb4b1e9d..f0692f9d 100644 --- a/hub/catalog_factories/data_models/energy_systems/generation_system.py +++ b/hub/catalog_factories/data_models/energy_systems/generation_system.py @@ -20,7 +20,7 @@ class GenerationSystem(ABC): """ def __init__(self, system_id, name, model_name=None, manufacturer=None, fuel_type=None, - distribution_systems=None, energy_storage_systems=None): + distribution_systems=None, energy_storage_systems=None, number_of_units=None): self._system_id = system_id self._name = name self._model_name = model_name @@ -28,6 +28,7 @@ class GenerationSystem(ABC): self._fuel_type = fuel_type self._distribution_systems = distribution_systems self._energy_storage_systems = energy_storage_systems + self._number_of_units = number_of_units @property def id(self): @@ -93,6 +94,14 @@ class GenerationSystem(ABC): """ return self._energy_storage_systems + @property + def number_of_units(self): + """ + Get the number of a specific generation unit + :return: int + """ + return self._number_of_units + def to_dictionary(self): """Class content to dictionary""" raise NotImplementedError diff --git a/hub/catalog_factories/data_models/energy_systems/non_pv_generation_system.py b/hub/catalog_factories/data_models/energy_systems/non_pv_generation_system.py index 5b203d01..10481e71 100644 --- a/hub/catalog_factories/data_models/energy_systems/non_pv_generation_system.py +++ b/hub/catalog_factories/data_models/energy_systems/non_pv_generation_system.py @@ -25,9 +25,11 @@ class NonPvGenerationSystem(GenerationSystem): maximum_cooling_supply_temperature=None, minimum_cooling_supply_temperature=None, heat_output_curve=None, heat_fuel_consumption_curve=None, heat_efficiency_curve=None, cooling_output_curve=None, cooling_fuel_consumption_curve=None, cooling_efficiency_curve=None, - distribution_systems=None, energy_storage_systems=None, dual_supply_capability=False): + distribution_systems=None, energy_storage_systems=None, domestic_hot_water=False, + heat_supply_temperature=None, cooling_supply_temperature=None, number_of_units=None, reversible=None, + simultaneous_heat_cold=None): super().__init__(system_id=system_id, name=name, model_name=model_name, manufacturer=manufacturer, fuel_type=fuel_type, - distribution_systems=distribution_systems, energy_storage_systems=energy_storage_systems) + distribution_systems=distribution_systems, energy_storage_systems=energy_storage_systems, number_of_units=number_of_units) self._system_type = system_type self._nominal_heat_output = nominal_heat_output self._maximum_heat_output = maximum_heat_output @@ -53,7 +55,11 @@ class NonPvGenerationSystem(GenerationSystem): self._cooling_output_curve = cooling_output_curve self._cooling_fuel_consumption_curve = cooling_fuel_consumption_curve self._cooling_efficiency_curve = cooling_efficiency_curve - self._dual_supply_capability = dual_supply_capability + self._domestic_hot_water = domestic_hot_water + self._heat_supply_temperature = heat_supply_temperature + self._cooling_supply_temperature = cooling_supply_temperature + self._reversible = reversible + self._simultaneous_heat_cold = simultaneous_heat_cold @property def system_type(self): @@ -256,12 +262,44 @@ class NonPvGenerationSystem(GenerationSystem): return self._cooling_efficiency_curve @property - def dual_supply_capability(self): + def domestic_hot_water(self): """ - Get dual supply capability + Get the ability to produce domestic hot water :return: bool """ - return self._dual_supply_capability + return self._domestic_hot_water + + @property + def heat_supply_temperature(self): + """ + Get heat supply temperature + :return: list + """ + return self._heat_supply_temperature + + @property + def cooling_supply_temperature(self): + """ + Get heat supply temperature + :return: list + """ + return self._cooling_supply_temperature + + @property + def reversibility(self): + """ + Get the ability to produce heating and cooling + :return: bool + """ + return self._reversible + + @property + def simultaneous_heat_cold(self): + """ + Get the ability to produce heating and cooling at the same time + :return: bool + """ + return self._simultaneous_heat_cold def to_dictionary(self): """Class content to dictionary""" @@ -304,7 +342,12 @@ class NonPvGenerationSystem(GenerationSystem): 'cooling efficiency curve': self.cooling_efficiency_curve, 'distribution systems connected': _distribution_systems, 'storage systems connected': _energy_storage_systems, - 'dual supply capability': self.dual_supply_capability + 'domestic hot water production capability': self.domestic_hot_water, + 'heat supply temperature': self.heat_supply_temperature, + 'cooling supply temperature': self.cooling_supply_temperature, + 'number of units': self.number_of_units, + 'reversible cycle': self.reversibility, + 'simultaneous heat and cooling production': self.simultaneous_heat_cold } } return content diff --git a/hub/catalog_factories/data_models/energy_systems/pv_generation_system.py b/hub/catalog_factories/data_models/energy_systems/pv_generation_system.py index 87228afa..319070c0 100644 --- a/hub/catalog_factories/data_models/energy_systems/pv_generation_system.py +++ b/hub/catalog_factories/data_models/energy_systems/pv_generation_system.py @@ -18,10 +18,10 @@ class PvGenerationSystem(GenerationSystem): nominal_electricity_output=None, nominal_ambient_temperature=None, nominal_cell_temperature=None, nominal_radiation=None, standard_test_condition_cell_temperature=None, standard_test_condition_maximum_power=None, cell_temperature_coefficient=None, width=None, height=None, - distribution_systems=None, energy_storage_systems=None): + distribution_systems=None, energy_storage_systems=None, number_of_units=None): super().__init__(system_id=system_id, name=name, model_name=model_name, manufacturer=manufacturer, fuel_type='renewable', distribution_systems=distribution_systems, - energy_storage_systems=energy_storage_systems) + energy_storage_systems=energy_storage_systems, number_of_units=number_of_units) self._system_type = system_type self._electricity_efficiency = electricity_efficiency self._nominal_electricity_output = nominal_electricity_output @@ -147,7 +147,8 @@ class PvGenerationSystem(GenerationSystem): 'width': self.width, 'height': self.height, 'distribution systems connected': _distribution_systems, - 'storage systems connected': _energy_storage_systems + 'storage systems connected': _energy_storage_systems, + 'number of units': self.number_of_units } } return content diff --git a/hub/catalog_factories/data_models/energy_systems/thermal_storage_system.py b/hub/catalog_factories/data_models/energy_systems/thermal_storage_system.py index d3cdf255..ca773e09 100644 --- a/hub/catalog_factories/data_models/energy_systems/thermal_storage_system.py +++ b/hub/catalog_factories/data_models/energy_systems/thermal_storage_system.py @@ -17,7 +17,7 @@ class ThermalStorageSystem(EnergyStorageSystem): def __init__(self, storage_id, type_energy_stored=None, model_name=None, manufacturer=None, storage_type=None, nominal_capacity=None, losses_ratio=None, volume=None, height=None, layers=None, - maximum_operating_temperature=None, storage_medium=None): + maximum_operating_temperature=None, storage_medium=None, heating_coil_capacity=None): super().__init__(storage_id, model_name, manufacturer, nominal_capacity, losses_ratio) self._type_energy_stored = type_energy_stored @@ -27,6 +27,7 @@ class ThermalStorageSystem(EnergyStorageSystem): self._layers = layers self._maximum_operating_temperature = maximum_operating_temperature self._storage_medium = storage_medium + self._heating_coil_capacity = heating_coil_capacity @property def type_energy_stored(self): @@ -84,6 +85,14 @@ class ThermalStorageSystem(EnergyStorageSystem): """ return self._storage_medium + @property + def heating_coil_capacity(self): + """ + Get heating coil capacity in Watts + :return: [material + """ + return self._heating_coil_capacity + def to_dictionary(self): """Class content to dictionary""" _layers = None @@ -110,7 +119,8 @@ class ThermalStorageSystem(EnergyStorageSystem): 'height [m]': self.height, 'layers': _layers, 'maximum operating temperature [Celsius]': self.maximum_operating_temperature, - 'storage_medium': self.storage_medium.to_dictionary() + 'storage_medium': self.storage_medium.to_dictionary(), + 'heating coil capacity [W]': self.heating_coil_capacity } } return content diff --git a/hub/catalog_factories/energy_systems/__pycache__/montreal_custom_catalog.cpython-39.pyc b/hub/catalog_factories/energy_systems/__pycache__/montreal_custom_catalog.cpython-39.pyc index e6e57547..18eeb41c 100644 Binary files a/hub/catalog_factories/energy_systems/__pycache__/montreal_custom_catalog.cpython-39.pyc and b/hub/catalog_factories/energy_systems/__pycache__/montreal_custom_catalog.cpython-39.pyc differ diff --git a/hub/catalog_factories/energy_systems/__pycache__/montreal_future_system_catalogue.cpython-39.pyc b/hub/catalog_factories/energy_systems/__pycache__/montreal_future_system_catalogue.cpython-39.pyc index 4987531e..0ab98f97 100644 Binary files a/hub/catalog_factories/energy_systems/__pycache__/montreal_future_system_catalogue.cpython-39.pyc and b/hub/catalog_factories/energy_systems/__pycache__/montreal_future_system_catalogue.cpython-39.pyc differ diff --git a/hub/catalog_factories/energy_systems/montreal_custom_catalog.py b/hub/catalog_factories/energy_systems/montreal_custom_catalog.py index d731f781..d3e37e36 100644 --- a/hub/catalog_factories/energy_systems/montreal_custom_catalog.py +++ b/hub/catalog_factories/energy_systems/montreal_custom_catalog.py @@ -87,7 +87,7 @@ class MontrealCustomCatalog(Catalog): cooling_efficiency=cooling_efficiency, electricity_efficiency=electricity_efficiency, energy_storage_systems=storage_systems, - dual_supply_capability=False + domestic_hot_water=False ) _equipments.append(generation_system) diff --git a/hub/catalog_factories/energy_systems/montreal_future_system_catalogue.py b/hub/catalog_factories/energy_systems/montreal_future_system_catalogue.py index d2eb35c3..f234ace4 100644 --- a/hub/catalog_factories/energy_systems/montreal_future_system_catalogue.py +++ b/hub/catalog_factories/energy_systems/montreal_future_system_catalogue.py @@ -121,12 +121,30 @@ class MontrealFutureSystemCatalogue(Catalog): parameters = non_pv['cooling_efficiency_curve']['parameters'] coefficients = list(non_pv['cooling_efficiency_curve']['coefficients'].values()) cooling_efficiency_curve = PerformanceCurves(curve_type, dependant_variable, parameters, coefficients) - dual_supply_capability = None - if non_pv['dual_supply_capability'] is not None: - if non_pv['dual_supply_capability'] == 'True': - dual_supply_capability = True + dhw = None + if non_pv['domestic_hot_water'] is not None: + if non_pv['domestic_hot_water'] == 'True': + dhw = True else: - dual_supply_capability = False + dhw = False + + reversible = None + if non_pv['reversible'] is not None: + if non_pv['reversible'] == 'True': + reversible = True + else: + reversible = False + + dual_supply = None + if non_pv['simultaneous_heat_cold'] is not None: + if non_pv['simultaneous_heat_cold'] == 'True': + dual_supply = True + else: + dual_supply = False + + heat_supply_temperature = None + cooling_supply_temperature = None + number_of_units = non_pv['number_of_units'] non_pv_component = NonPvGenerationSystem(system_id=system_id, name=name, @@ -160,7 +178,12 @@ class MontrealFutureSystemCatalogue(Catalog): cooling_efficiency_curve=cooling_efficiency_curve, distribution_systems=distribution_systems, energy_storage_systems=energy_storage_systems, - dual_supply_capability=dual_supply_capability) + domestic_hot_water=dhw, + heat_supply_temperature=heat_supply_temperature, + cooling_supply_temperature=cooling_supply_temperature, + number_of_units=number_of_units, + reversible=reversible, + simultaneous_heat_cold=dual_supply) generation_components.append(non_pv_component) pv_generation_components = self._archetypes['EnergySystemCatalog']['energy_generation_components'][ 'pv_generation_component'] @@ -187,7 +210,7 @@ class MontrealFutureSystemCatalogue(Catalog): storage_component = pv['energy_storage_systems']['storage_id'] storage_systems = self._search_storage_equipment(self._load_storage_components(), storage_component) energy_storage_systems = storage_systems - + number_of_units = pv['number_of_units'] pv_component = PvGenerationSystem(system_id=system_id, name=name, system_type=system_type, @@ -205,7 +228,8 @@ class MontrealFutureSystemCatalogue(Catalog): width=width, height=height, distribution_systems=distribution_systems, - energy_storage_systems=energy_storage_systems) + energy_storage_systems=energy_storage_systems, + number_of_units=number_of_units) generation_components.append(pv_component) return generation_components @@ -284,6 +308,7 @@ class MontrealFutureSystemCatalogue(Catalog): layers = [insulation_layer, tank_layer] nominal_capacity = tes['nominal_capacity'] losses_ratio = tes['losses_ratio'] + heating_coil_capacity = None storage_component = ThermalStorageSystem(storage_id=storage_id, model_name=model_name, type_energy_stored=type_energy_stored, @@ -295,7 +320,8 @@ class MontrealFutureSystemCatalogue(Catalog): height=height, layers=layers, maximum_operating_temperature=maximum_operating_temperature, - storage_medium=medium) + storage_medium=medium, + heating_coil_capacity=heating_coil_capacity) storage_components.append(storage_component) for template in template_storages: @@ -303,7 +329,7 @@ class MontrealFutureSystemCatalogue(Catalog): storage_type = template['storage_type'] type_energy_stored = template['type_energy_stored'] maximum_operating_temperature = template['maximum_operating_temperature'] - height = template['physical_characteristics']['height'] + height = float(template['physical_characteristics']['height']) materials = self._load_materials() insulation_material_id = template['insulation']['material_id'] insulation_material = self._search_material(materials, insulation_material_id) @@ -322,6 +348,7 @@ class MontrealFutureSystemCatalogue(Catalog): nominal_capacity = template['nominal_capacity'] losses_ratio = template['losses_ratio'] volume = template['physical_characteristics']['volume'] + heating_coil_capacity = None storage_component = ThermalStorageSystem(storage_id=storage_id, model_name=model_name, type_energy_stored=type_energy_stored, @@ -333,7 +360,8 @@ class MontrealFutureSystemCatalogue(Catalog): height=height, layers=layers, maximum_operating_temperature=maximum_operating_temperature, - storage_medium=medium) + storage_medium=medium, + heating_coil_capacity=heating_coil_capacity) storage_components.append(storage_component) return storage_components diff --git a/hub/city_model_structure/building.py b/hub/city_model_structure/building.py index c01e5bf2..95f0f167 100644 --- a/hub/city_model_structure/building.py +++ b/hub/city_model_structure/building.py @@ -91,6 +91,9 @@ class Building(CityObject): else: logging.error('Building %s [%s] has an unexpected surface type %s.', self.name, self.aliases, surface.type) self._heating_consumption_disaggregated = {} + self._domestic_hot_water_peak_load = None + self._fuel_consumption_breakdown = {} + self._domestic_how_water_consumption_disaggregated = {} @property def shell(self) -> Polyhedron: @@ -470,6 +473,22 @@ class Building(CityObject): results[cte.YEAR] = [max(monthly_values)] return results + @property + def domestic_hot_water_peak_load(self) -> Union[None, dict]: + """ + Get cooling peak load in W + :return: dict{[float]} + """ + results = {} + monthly_values = None + if cte.HOUR in self.domestic_hot_water_heat_demand: + monthly_values = PeakLoads().peak_loads_from_hourly(self.domestic_hot_water_heat_demand[cte.HOUR]) + if monthly_values is None: + return None + results[cte.MONTH] = [x for x in monthly_values] + results[cte.YEAR] = [max(monthly_values)] + return results + @property def eave_height(self): """ @@ -841,6 +860,21 @@ class Building(CityObject): """ self._heating_consumption_disaggregated = value + @property + def domestic_how_water_consumption_disaggregated(self) -> dict: + """ + Get energy consumed for heating from different fuels in J + return: dict + """ + return self._domestic_how_water_consumption_disaggregated + + @domestic_how_water_consumption_disaggregated.setter + def domestic_how_water_consumption_disaggregated(self, value): + """ + Get energy consumed for heating from different fuels in J + return: dict + """ + self._domestic_how_water_consumption_disaggregated = value @property def lower_corner(self): @@ -855,3 +889,47 @@ class Building(CityObject): Get building upper corner. """ return [self._max_x, self._max_y, self._max_z] + + @property + def fuel_consumption_breakdown(self) -> dict: + """ + Get energy consumption of different sectors + return: dict + """ + fuel_breakdown = {cte.ELECTRICITY: {cte.LIGHTING: self.lighting_electrical_demand[cte.YEAR][0], + cte.APPLIANCES: self.appliances_electrical_demand[cte.YEAR][0]}} + energy_systems = self.energy_systems + for energy_system in energy_systems: + demand_types = energy_system.demand_types + generation_systems = energy_system.generation_systems + for demand_type in demand_types: + if demand_type == cte.COOLING: + fuel_breakdown[cte.ELECTRICITY][cte.COOLING] = self.cooling_consumption[cte.YEAR][0] / 3600 + elif demand_type == cte.HEATING: + heating_fuels = [generation_system.fuel_type for generation_system in generation_systems] + if len(heating_fuels) > 1: + for fuel in heating_fuels: + if fuel == cte.ELECTRICITY: + fuel_breakdown[cte.ELECTRICITY][cte.HEATING] = self._heating_consumption_disaggregated[cte.ELECTRICITY][cte.YEAR][0] + elif fuel not in fuel_breakdown.keys(): + fuel_breakdown[fuel] = {cte.HEATING: self._heating_consumption_disaggregated[fuel][cte.YEAR][0]} + else: + fuel_breakdown[fuel][cte.HEATING] = self._heating_consumption_disaggregated[fuel][cte.YEAR][0] + else: + fuel = heating_fuels[0] + if fuel == cte.ELECTRICITY: + fuel_breakdown[cte.ELECTRICITY][cte.HEATING] = self.heating_consumption[cte.YEAR][0] + elif fuel not in fuel_breakdown.keys(): + fuel_breakdown[fuel] = {cte.HEATING: self.heating_consumption[cte.YEAR][0]} + else: + fuel_breakdown[fuel][cte.HEATING] = self.heating_consumption[cte.YEAR][0] + elif demand_type == cte.DOMESTIC_HOT_WATER: + for generation_system in generation_systems: + if generation_system.fuel_type == cte.ELECTRICITY: + fuel_breakdown[cte.ELECTRICITY][cte.DOMESTIC_HOT_WATER] = self.domestic_hot_water_consumption[cte.YEAR][0] + elif generation_system.fuel_type not in fuel_breakdown.keys(): + fuel_breakdown[generation_system.fuel_type] = {cte.DOMESTIC_HOT_WATER: self.domestic_hot_water_consumption[cte.YEAR][0]} + else: + fuel_breakdown[generation_system.fuel_type][cte.DOMESTIC_HOT_WATER] = self.domestic_hot_water_consumption[cte.YEAR][0] + self._fuel_consumption_breakdown = fuel_breakdown + return self._fuel_consumption_breakdown diff --git a/hub/city_model_structure/energy_systems/__pycache__/generation_system.cpython-39.pyc b/hub/city_model_structure/energy_systems/__pycache__/generation_system.cpython-39.pyc index eccddbab..4d2de077 100644 Binary files a/hub/city_model_structure/energy_systems/__pycache__/generation_system.cpython-39.pyc and b/hub/city_model_structure/energy_systems/__pycache__/generation_system.cpython-39.pyc differ diff --git a/hub/city_model_structure/energy_systems/__pycache__/non_pv_generation_system.cpython-39.pyc b/hub/city_model_structure/energy_systems/__pycache__/non_pv_generation_system.cpython-39.pyc index b51798c1..0c6b8d45 100644 Binary files a/hub/city_model_structure/energy_systems/__pycache__/non_pv_generation_system.cpython-39.pyc and b/hub/city_model_structure/energy_systems/__pycache__/non_pv_generation_system.cpython-39.pyc differ diff --git a/hub/city_model_structure/energy_systems/__pycache__/thermal_storage_system.cpython-39.pyc b/hub/city_model_structure/energy_systems/__pycache__/thermal_storage_system.cpython-39.pyc index c27735b2..cb1c3543 100644 Binary files a/hub/city_model_structure/energy_systems/__pycache__/thermal_storage_system.cpython-39.pyc and b/hub/city_model_structure/energy_systems/__pycache__/thermal_storage_system.cpython-39.pyc differ diff --git a/hub/city_model_structure/energy_systems/generation_system.py b/hub/city_model_structure/energy_systems/generation_system.py index 394c2f09..4d06eb14 100644 --- a/hub/city_model_structure/energy_systems/generation_system.py +++ b/hub/city_model_structure/energy_systems/generation_system.py @@ -12,6 +12,8 @@ from typing import Union, List from hub.city_model_structure.energy_systems.distribution_system import DistributionSystem from hub.city_model_structure.energy_systems.energy_storage_system import EnergyStorageSystem +from hub.city_model_structure.energy_systems.thermal_storage_system import ThermalStorageSystem +from hub.city_model_structure.energy_systems.electrical_storage_system import ElectricalStorageSystem class GenerationSystem(ABC): @@ -26,6 +28,7 @@ class GenerationSystem(ABC): self._fuel_type = None self._distribution_systems = None self._energy_storage_systems = None + self._number_of_units = None @property def system_type(self): @@ -124,7 +127,7 @@ class GenerationSystem(ABC): self._distribution_systems = value @property - def energy_storage_systems(self) -> Union[None, List[EnergyStorageSystem]]: + def energy_storage_systems(self) -> Union[None, List[ThermalStorageSystem], List[ElectricalStorageSystem]]: """ Get energy storage systems connected to this generation system :return: [EnergyStorageSystem] @@ -138,3 +141,19 @@ class GenerationSystem(ABC): :param value: [EnergyStorageSystem] """ self._energy_storage_systems = value + + @property + def number_of_units(self): + """ + Get number of a specific generation unit + :return: int + """ + return self._number_of_units + + @number_of_units.setter + def number_of_units(self, value): + """ + Set number of a specific generation unit + :return: int + """ + self._number_of_units = value diff --git a/hub/city_model_structure/energy_systems/non_pv_generation_system.py b/hub/city_model_structure/energy_systems/non_pv_generation_system.py index fea1d7b2..e5df16cd 100644 --- a/hub/city_model_structure/energy_systems/non_pv_generation_system.py +++ b/hub/city_model_structure/energy_systems/non_pv_generation_system.py @@ -42,7 +42,11 @@ class NonPvGenerationSystem(GenerationSystem): self._cooling_output_curve = None self._cooling_fuel_consumption_curve = None self._cooling_efficiency_curve = None - self._dual_supply_capability = None + self._domestic_hot_water = None + self._heat_supply_temperature = None + self._cooling_supply_temperature = None + self._reversible = None + self._simultaneous_heat_cold = None @property def nominal_heat_output(self): @@ -429,21 +433,90 @@ class NonPvGenerationSystem(GenerationSystem): self._cooling_efficiency_curve = value @property - def dual_supply_capability(self): + def domestic_hot_water(self): """ - Get the capability of the generation component for simultaneous heat and cold production + Get the capability of generating domestic hot water :return: bool """ - return self._dual_supply_capability + return self._domestic_hot_water - @dual_supply_capability.setter - def dual_supply_capability(self, value): + @domestic_hot_water.setter + def domestic_hot_water(self, value): """ - Set the capability of the generation component for simultaneous heat and cold production + Set the capability of generating domestic hot water :return: bool """ - self._dual_supply_capability = value + self._domestic_hot_water = value + @property + def heat_supply_temperature(self): + """ + Get the hourly heat supply temperature + :return: list + """ + return self._heat_supply_temperature + + @heat_supply_temperature.setter + def heat_supply_temperature(self, value): + """ + set the hourly heat supply temperature + :param value: + :return: list + """ + self._heat_supply_temperature = value + + @property + def cooling_supply_temperature(self): + """ + Get the hourly cooling supply temperature + :return: list + """ + return self._heat_supply_temperature + + @cooling_supply_temperature.setter + def cooling_supply_temperature(self, value): + """ + set the hourly cooling supply temperature + :param value: + :return: list + """ + self._cooling_supply_temperature = value + + @property + def reversibility(self): + """ + Get the capability of generating both heating and cooling + + :return: bool + """ + return self._reversible + + @reversibility.setter + def reversibility(self, value): + """ + Set the capability of generating domestic hot water + + :return: bool + """ + self._reversible = value + + @property + def simultaneous_heat_cold(self): + """ + Get the capability of generating both heating and cooling at the same time + + :return: bool + """ + return self._simultaneous_heat_cold + + @simultaneous_heat_cold.setter + def simultaneous_heat_cold(self, value): + """ + Set the capability of generating domestic hot water at the same time + + :return: bool + """ + self._simultaneous_heat_cold = value diff --git a/hub/city_model_structure/energy_systems/thermal_storage_system.py b/hub/city_model_structure/energy_systems/thermal_storage_system.py index 0f7f6a12..30e1579e 100644 --- a/hub/city_model_structure/energy_systems/thermal_storage_system.py +++ b/hub/city_model_structure/energy_systems/thermal_storage_system.py @@ -22,6 +22,7 @@ class ThermalStorageSystem(EnergyStorageSystem): self._height = None self._layers = None self._maximum_operating_temperature = None + self._heating_coil_capacity = None @property def volume(self): @@ -86,3 +87,19 @@ class ThermalStorageSystem(EnergyStorageSystem): :param value: float """ self._maximum_operating_temperature = value + + @property + def heating_coil_capacity(self): + """ + Get heating coil capacity in Watts + :return: float + """ + return self._maximum_operating_temperature + + @heating_coil_capacity.setter + def heating_coil_capacity(self, value): + """ + Set heating coil capacity in Watts + :param value: float + """ + self._heating_coil_capacity = value diff --git a/hub/data/costs/montreal_costs_completed.xml b/hub/data/costs/montreal_costs_completed.xml index 64be368c..5ce1fde9 100644 --- a/hub/data/costs/montreal_costs_completed.xml +++ b/hub/data/costs/montreal_costs_completed.xml @@ -38,38 +38,33 @@ 25 - 47.62 - 47.62 + 550 + 550 15 - 47.62 - 47.62 + 700 + 700 15 - 47.62 - 47.62 + 850 + 850 15 - 47.62 - 47.62 + 480 + 480 15 - - 47.62 - 47.62 - 15 - - 47.62 - 47.62 + 350 + 350 15 - 47.62 - 47.62 + 300 + 300 15 @@ -90,26 +85,26 @@ - 47.62 - 47.62 + 50 + 50 15 - 47.62 - 47.62 + 80 + 80 15 - 47.62 - 47.62 + 850 + 850 15 - 47.62 - 47.62 + 50 + 50 15 @@ -128,20 +123,30 @@ - - 12.27 - 0 - 0.075 + + 12.27 + 0 + 0.075 + + - + 17.71 0.0640 + 0.777 + 47.1 - + + 1.2 + 0.846 + 42.6 - - 0.04 + + + 0.04 + + 18 @@ -201,78 +206,73 @@ 25 - 47.62 - 47.62 + 550 + 550 15 - 47.62 - 47.62 + 700 + 700 15 - 47.62 - 47.62 + 850 + 850 15 - 47.62 - 47.62 + 480 + 480 15 - - 47.62 - 47.62 - 15 - - 47.62 - 47.62 + 350 + 350 15 - 47.62 - 47.62 + 300 + 300 15 - + 622.86 622.86 15 - + - 0 + 0 0 15 - + 47.62 47.62 15 - + - 47.62 - 47.62 + 50 + 50 15 - 47.62 - 47.62 + 80 + 80 15 - 47.62 - 47.62 + 850 + 850 15 - 47.62 - 47.62 + 50 + 50 15 @@ -291,20 +291,28 @@ - + 12.27 0 0.075 - + 17.71 0.0640 + 0.777 + 47.1 - + + 1.2 + 0.846 + 42.6 - - 0.04 + + + 0.04 + + 18 diff --git a/hub/data/energy_systems/montreal_future_systems.xml b/hub/data/energy_systems/montreal_future_systems.xml index 769f87bd..4437fe62 100644 --- a/hub/data/energy_systems/montreal_future_systems.xml +++ b/hub/data/energy_systems/montreal_future_systems.xml @@ -5,11 +5,11 @@ 1 Water - - - - - + + + + + 981.0 4180.0 0.6 @@ -26,7 +26,7 @@ 4.7 23.5 0.95 - True + False natural gas @@ -50,7 +50,11 @@ - + True + + + + False 2 @@ -62,7 +66,7 @@ 6.15 30.8 0.95 - True + False natural gas @@ -86,7 +90,11 @@ - + True + + + + False 3 @@ -98,7 +106,7 @@ 8.8 44 0.95 - True + False natural gas @@ -122,7 +130,11 @@ - + True + + + + False 4 @@ -134,7 +146,7 @@ 12.3 61.5 0.95 - True + False natural gas @@ -158,7 +170,11 @@ - + True + + + + False 5 @@ -170,7 +186,7 @@ 4.0 35.2 0.95 - True + False natural gas @@ -194,7 +210,11 @@ - + True + + + + False 6 @@ -206,7 +226,7 @@ 4.0 35.2 0.95 - False + False natural gas @@ -230,7 +250,11 @@ - + True + + + + False 7 @@ -242,7 +266,7 @@ 2.5 25.0 0.96 - + False natural gas @@ -266,7 +290,11 @@ - + True + + + + False 8 @@ -278,7 +306,7 @@ 3.2 32.0 0.96 - + False natural gas @@ -302,7 +330,11 @@ - + True + + + + False 9 @@ -314,7 +346,7 @@ 4.5 45.0 0.95 - True + False natural gas @@ -338,7 +370,11 @@ - + True + + + + False 10 @@ -350,7 +386,7 @@ 3.5 35.0 0.95 - True + False natural gas @@ -374,7 +410,11 @@ - + True + + + + False 11 @@ -386,7 +426,7 @@ 5.3 53.0 0.95 - True + False natural gas @@ -410,7 +450,11 @@ - + True + + + + False 12 @@ -430,6 +474,7 @@ 1.048 + 13 @@ -441,10 +486,10 @@ 0 51.7 3.32 - + True Electricity Air - water + Water @@ -471,7 +516,11 @@ - False + False + + + + False 14 @@ -483,10 +532,10 @@ 0 279.3 3.07 - + True Electricity Air - water + Water @@ -513,7 +562,11 @@ - False + False + + + + False 15 @@ -525,10 +578,10 @@ 0 557 3.46 - + True Electricity Air - water + Water @@ -555,7 +608,11 @@ - False + False + + + + False 16 @@ -567,7 +624,7 @@ 0.90 - + False natural gas @@ -593,7 +650,11 @@ 6 - + True + + + + False 17 @@ -605,7 +666,7 @@ 0.95 - + False electricity @@ -631,11 +692,15 @@ 6 - + False + + + + False 18 - template Air-to-Water heat pump + template Air-to-Water heat pump with storage heat pump @@ -643,10 +708,10 @@ 3 - + True electricity Air - water + Water @@ -669,11 +734,15 @@ 6 - True + True + + + + True 19 - template Groundwater-to-Water heat pump + template Groundwater-to-Water heat pump with storage heat pump @@ -681,10 +750,10 @@ 3.5 - + True electricity Ground - water + Water @@ -707,11 +776,15 @@ 6 - True + True + + + + True 20 - template Water-to-Water heat pump + template Water-to-Water heat pump with storage heat pump @@ -719,10 +792,10 @@ 3.5 - + True electricity Water - water + Water @@ -745,7 +818,11 @@ 6 - True + True + + + + False 21 @@ -757,7 +834,7 @@ 0.90 - + False natural gas @@ -781,7 +858,11 @@ - + True + + + + False 22 @@ -793,7 +874,7 @@ 0.95 - + False electricity @@ -817,7 +898,11 @@ - + True + + + + False 23 @@ -829,10 +914,10 @@ 3 - + True electricity Air - water + Water @@ -853,7 +938,11 @@ - True + True + + + + True 24 @@ -865,10 +954,10 @@ 3.5 - + True electricity Ground - water + Water @@ -889,7 +978,11 @@ - True + True + + + + True 25 @@ -901,10 +994,10 @@ 3.5 - + True electricity Water - water + Water @@ -925,7 +1018,11 @@ - True + True + + + + True 26 @@ -945,7 +1042,51 @@ 1.0 + + False + + 27 + template domestic hot water heat pump + heat pump + + + + + + 3.5 + + electricity + Water + Water + + + + + + + + + + + + + + + + + + + + + 7 + + True + + + + False + @@ -970,8 +1111,9 @@ 1 sensible - - + + + 2 @@ -995,8 +1137,9 @@ 1 sensible - - + + + 3 @@ -1020,8 +1163,9 @@ 1 sensible - - + + + 4 @@ -1044,8 +1188,9 @@ 1 sensible - - + + + 5 @@ -1069,15 +1214,16 @@ 1 sensible - - + + + 6 template Hot Water Storage Tank thermal - HF 200 - + + 95.0 1 @@ -1088,47 +1234,74 @@ 0 1.5 Steel - + 1 sensible - - + + + + + + 7 + template Hot Water Storage Tank with Heating Coil + thermal + + + 95.0 + + 1 + 90.0 + + + 2 + 0 + 1.5 + Steel + + + + 1 + + sensible + + + 1 Polyurethane - - - - - - - + + + + + + + 0.028 2 Steel - - - - - - - + + + + + + + 18 - + - + @@ -1226,7 +1399,45 @@ 26 + + 8 + 4 pipe system with air source heat pump storage and gas boiler + schemas/ASHP+TES+GasBoiler.jpg + + heating + cooling + + + 21 + 18 + + + + 9 + 4 pipe system with air source heat pump storage and electric boiler + schemas/ASHP+TES+GasBoiler.jpg + + heating + cooling + + + 22 + 18 + + + + 10 + Domestic Hot Water Heat Pump with Coiled Storage + schemas/ASHP+TES+GasBoiler.jpg + + domestic_hot_water + + + 27 + + + PV+ASHP+GasBoiler+TES @@ -1306,6 +1517,14 @@ 6 + + PV+4Pipe+DHW + + 7 + 8 + 10 + + diff --git a/hub/helpers/constants.py b/hub/helpers/constants.py index 23fab290..a48718bd 100644 --- a/hub/helpers/constants.py +++ b/hub/helpers/constants.py @@ -292,6 +292,7 @@ WOOD = 'Wood' GAS = 'Gas' DIESEL = 'Diesel' COAL = 'Coal' +BIOMASS = 'Biomass' AIR = 'Air' WATER = 'Water' GEOTHERMAL = 'Geothermal' diff --git a/hub/imports/energy_systems/__pycache__/montreal_custom_energy_system_parameters.cpython-39.pyc b/hub/imports/energy_systems/__pycache__/montreal_custom_energy_system_parameters.cpython-39.pyc index 6c52da3b..4e8f384a 100644 Binary files a/hub/imports/energy_systems/__pycache__/montreal_custom_energy_system_parameters.cpython-39.pyc and b/hub/imports/energy_systems/__pycache__/montreal_custom_energy_system_parameters.cpython-39.pyc differ diff --git a/hub/imports/energy_systems/__pycache__/montreal_future_energy_systems_parameters.cpython-39.pyc b/hub/imports/energy_systems/__pycache__/montreal_future_energy_systems_parameters.cpython-39.pyc index 30d50078..24cbd513 100644 Binary files a/hub/imports/energy_systems/__pycache__/montreal_future_energy_systems_parameters.cpython-39.pyc and b/hub/imports/energy_systems/__pycache__/montreal_future_energy_systems_parameters.cpython-39.pyc differ diff --git a/hub/imports/energy_systems/montreal_custom_energy_system_parameters.py b/hub/imports/energy_systems/montreal_custom_energy_system_parameters.py index 348df9fe..00f84d3b 100644 --- a/hub/imports/energy_systems/montreal_custom_energy_system_parameters.py +++ b/hub/imports/energy_systems/montreal_custom_energy_system_parameters.py @@ -113,7 +113,7 @@ class MontrealCustomEnergySystemParameters: else: _generic_storage_system = ThermalStorageSystem() _generic_storage_system.type_energy_stored = 'thermal' - _generation_system.energy_storage_systems = [_generic_storage_system] + _generation_system.energy_storage_systems = _generic_storage_system _generation_systems.append(_generation_system) return _generation_systems diff --git a/hub/imports/energy_systems/montreal_future_energy_systems_parameters.py b/hub/imports/energy_systems/montreal_future_energy_systems_parameters.py index 542608a0..8612f845 100644 --- a/hub/imports/energy_systems/montreal_future_energy_systems_parameters.py +++ b/hub/imports/energy_systems/montreal_future_energy_systems_parameters.py @@ -140,12 +140,13 @@ class MontrealFutureEnergySystemParameters: _generation_system.cooling_output_curve = archetype_generation_system.cooling_output_curve _generation_system.cooling_fuel_consumption_curve = archetype_generation_system.cooling_fuel_consumption_curve _generation_system.cooling_efficiency_curve = archetype_generation_system.cooling_efficiency_curve - _generation_system.dual_supply_capability = archetype_generation_system.dual_supply_capability + _generation_system.domestic_hot_water = archetype_generation_system.domestic_hot_water _generation_system.nominal_electricity_output = archetype_generation_system.nominal_electricity_output _generation_system.source_medium = archetype_generation_system.source_medium _generation_system.heat_efficiency = archetype_generation_system.heat_efficiency _generation_system.cooling_efficiency = archetype_generation_system.cooling_efficiency _generation_system.electricity_efficiency = archetype_generation_system.electricity_efficiency + _generation_system.reversibility = archetype_generation_system.reversibility _generic_storage_system = None if archetype_generation_system.energy_storage_systems is not None: _storage_systems = [] @@ -155,11 +156,18 @@ class MontrealFutureEnergySystemParameters: _generic_storage_system.type_energy_stored = 'electrical' else: _generic_storage_system = ThermalStorageSystem() - _generic_storage_system.type_energy_stored = 'thermal' + _generic_storage_system.type_energy_stored = storage_system.type_energy_stored + _generic_storage_system.height = storage_system.height + _generic_storage_system.layers = storage_system.layers + _generic_storage_system.storage_medium = storage_system.storage_medium _storage_systems.append(_generic_storage_system) - _generation_system.energy_storage_systems = [_storage_systems] - if archetype_generation_system.dual_supply_capability: - _generation_system.dual_supply_capability = True + _generation_system.energy_storage_systems = _storage_systems + if archetype_generation_system.domestic_hot_water: + _generation_system.domestic_hot_water = True + if archetype_generation_system.reversibility: + _generation_system.reversibility = True + if archetype_generation_system.simultaneous_heat_cold: + _generation_system.simultaneous_heat_cold = True _generation_systems.append(_generation_system) return _generation_systems diff --git a/main.py b/main.py index e9c69db6..f89d37c9 100644 --- a/main.py +++ b/main.py @@ -12,6 +12,11 @@ from hub.exports.exports_factory import ExportsFactory from scripts.energy_system_analysis_report import EnergySystemAnalysisReport from scripts import random_assignation from hub.imports.energy_systems_factory import EnergySystemsFactory +from scripts.energy_system_sizing import SystemSizing +from scripts.system_simulation import SystemSimulation +from scripts.costs.cost import Cost +from costs.constants import CURRENT_STATUS, SKIN_RETROFIT, SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV, SYSTEM_RETROFIT_AND_PV +from scripts.energy_system_retrofit_results import system_results, new_system_results # Specify the GeoJSON file path geojson_file = process_geojson(x=-73.5681295982132, y=45.49218262677643, diff=0.0001) @@ -33,8 +38,21 @@ ExportsFactory('sra', city, output_path).export() sra_path = (output_path / f'{city.name}_sra.xml').resolve() subprocess.run(['sra', str(sra_path)]) ResultFactory('sra', city, output_path).enrich() -# Run EnergyPlus workflow energy_plus_workflow(city) random_assignation.call_random(city.buildings, random_assignation.residential_systems_percentage) EnergySystemsFactory('montreal_custom', city).enrich() -EnergySystemAnalysisReport(city, output_path).create_report() +SystemSizing(city.buildings).montreal_custom() +current_system = system_results(city.buildings) +new_system = system_results(city.buildings) +EnergySystemAnalysisReport(city, output_path).create_report(current_system, new_system) + +for building in city.buildings: + costs = Cost(building, retrofit_scenario=SYSTEM_RETROFIT_AND_PV).life_cycle + Cost(building, retrofit_scenario=SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV).life_cycle.to_csv(output_path / f'{building.name}_lcc.csv') + costs.loc['global_capital_costs', f'Scenario {SYSTEM_RETROFIT_AND_PV}'].to_csv(output_path / f'{building.name}_global_capital.csv') + costs.loc['global_operational_costs', f'Scenario {SYSTEM_RETROFIT_AND_PV}'].to_csv( + output_path / f'{building.name}_global_operational.csv') + + + + diff --git a/scripts/costs/capital_costs.py b/scripts/costs/capital_costs.py index 654218af..49f7efd0 100644 --- a/scripts/costs/capital_costs.py +++ b/scripts/costs/capital_costs.py @@ -11,15 +11,16 @@ import pandas as pd import numpy_financial as npf from hub.city_model_structure.building import Building import hub.helpers.constants as cte -from costs.configuration import Configuration -from costs.constants import SKIN_RETROFIT, SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV, SYSTEM_RETROFIT_AND_PV -from costs.cost_base import CostBase +from scripts.costs.configuration import Configuration +from scripts.costs.constants import SKIN_RETROFIT, SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV, SYSTEM_RETROFIT_AND_PV +from scripts.costs.cost_base import CostBase class CapitalCosts(CostBase): """ Capital costs class """ + def __init__(self, building: Building, configuration: Configuration): super().__init__(building, configuration) self._yearly_capital_costs = pd.DataFrame( @@ -28,25 +29,28 @@ class CapitalCosts(CostBase): 'B2010_opaque_walls', 'B2020_transparent', 'B3010_opaque_roof', - 'B10_superstructure', - 'D301010_photovoltaic_system', - 'D3020_heat_generating_systems', - 'D3030_cooling_generation_systems', + 'B1010_superstructure', + 'D2010_photovoltaic_system', + 'D3020_heat_and_cooling_generating_systems', 'D3040_distribution_systems', - 'D3080_other_hvac_ahu', + 'D3050_other_hvac_ahu', + 'D3060_storage_systems', + 'D40_dhw', 'D5020_lighting_and_branch_wiring' ], dtype='float' ) self._yearly_capital_costs.loc[0, 'B2010_opaque_walls'] = 0 - self._yearly_capital_costs.loc[0]['B2020_transparent'] = 0 + self._yearly_capital_costs.loc[0, 'B2020_transparent'] = 0 self._yearly_capital_costs.loc[0, 'B3010_opaque_roof'] = 0 - self._yearly_capital_costs.loc[0]['B10_superstructure'] = 0 - self._yearly_capital_costs.loc[0, 'D3020_heat_generating_systems'] = 0 - self._yearly_capital_costs.loc[0, 'D3030_cooling_generation_systems'] = 0 + self._yearly_capital_costs.loc[0, 'B1010_superstructure'] = 0 + self._yearly_capital_costs.loc[0, 'D2010_photovoltaic_system'] = 0 + self._yearly_capital_costs.loc[0, 'D3020_heat_and_cooling_generating_systems'] = 0 self._yearly_capital_costs.loc[0, 'D3040_distribution_systems'] = 0 self._yearly_capital_costs.loc[0, 'D3080_other_hvac_ahu'] = 0 - self._yearly_capital_costs.loc[0, 'D5020_lighting_and_branch_wiring'] = 0 + self._yearly_capital_costs.loc[0, 'D3060_storage_systems'] = 0 + self._yearly_capital_costs.loc[0, 'D40_dhw'] = 0 + # self._yearly_capital_costs.loc[0, 'D5020_lighting_and_branch_wiring'] = 0 self._yearly_capital_incomes = pd.DataFrame( index=self._rng, @@ -60,26 +64,32 @@ class CapitalCosts(CostBase): self._yearly_capital_incomes.loc[0, 'Subsidies construction'] = 0 self._yearly_capital_incomes.loc[0, 'Subsidies HVAC'] = 0 self._yearly_capital_incomes.loc[0, 'Subsidies PV'] = 0 + self._yearly_capital_costs.fillna(0, inplace=True) + self._own_capital = 1 - self._configuration.percentage_credit + self._surface_pv = 0 + for roof in self._building.roofs: + self._surface_pv += roof.solid_polygon.area * roof.solar_collectors_area_reduction_factor def calculate(self) -> tuple[pd.DataFrame, pd.DataFrame]: + if self._configuration.retrofit_scenario in (SKIN_RETROFIT, SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV): + self.skin_capital_cost() + if self._configuration.retrofit_scenario in (SYSTEM_RETROFIT_AND_PV, SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV): + self.energy_system_capital_cost() + + self.skin_yearly_capital_costs() + self.yearly_energy_system_costs() + self.yearly_incomes() + return self._yearly_capital_costs, self._yearly_capital_incomes + + def skin_capital_cost(self): """ - Calculate capital cost - :return: pd.DataFrame, pd.DataFrame + calculating skin costs + :return: """ surface_opaque = 0 surface_transparent = 0 surface_roof = 0 surface_ground = 0 - capital_cost_pv = 0 - capital_cost_opaque = 0 - capital_cost_ground = 0 - capital_cost_transparent = 0 - capital_cost_roof = 0 - capital_cost_heating_equipment = 0 - capital_cost_cooling_equipment = 0 - capital_cost_distribution_equipment = 0 - capital_cost_other_hvac_ahu = 0 - capital_cost_lighting = 0 for thermal_zone in self._building.thermal_zones_from_internal_zones: for thermal_boundary in thermal_zone.thermal_boundaries: @@ -91,144 +101,222 @@ class CapitalCosts(CostBase): surface_opaque += thermal_boundary.opaque_area * (1 - thermal_boundary.window_ratio) surface_transparent += thermal_boundary.opaque_area * thermal_boundary.window_ratio - peak_heating = self._building.heating_peak_load[cte.YEAR][0] / 1000 - peak_cooling = self._building.cooling_peak_load[cte.YEAR][0] / 1000 - - surface_pv = 0 - for roof in self._building.roofs: - surface_pv += roof.solid_polygon.area * roof.solar_collectors_area_reduction_factor - - self._yearly_capital_costs.fillna(0, inplace=True) - own_capital = 1 - self._configuration.percentage_credit - if self._configuration.retrofit_scenario in (SKIN_RETROFIT, SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV): - chapter = self._capital_costs_chapter.chapter('B_shell') - capital_cost_opaque = surface_opaque * chapter.item('B2010_opaque_walls').refurbishment[0] - capital_cost_transparent = surface_transparent * chapter.item('B2020_transparent').refurbishment[0] - capital_cost_roof = surface_roof * chapter.item('B3010_opaque_roof').refurbishment[0] - capital_cost_ground = surface_ground * chapter.item('B10_superstructure').refurbishment[0] - self._yearly_capital_costs.loc[0, 'B2010_opaque_walls'] = capital_cost_opaque * own_capital - self._yearly_capital_costs.loc[0]['B2020_transparent'] = capital_cost_transparent * own_capital - self._yearly_capital_costs.loc[0, 'B3010_opaque_roof'] = capital_cost_roof * own_capital - self._yearly_capital_costs.loc[0]['B10_superstructure'] = capital_cost_ground * own_capital - - if self._configuration.retrofit_scenario in (SYSTEM_RETROFIT_AND_PV, SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV): - chapter = self._capital_costs_chapter.chapter('D_services') - capital_cost_pv = surface_pv * chapter.item('D301010_photovoltaic_system').initial_investment[0] - capital_cost_heating_equipment = peak_heating * chapter.item('D3020_heat_generating_systems').initial_investment[0] - capital_cost_cooling_equipment = peak_cooling * chapter.item('D3030_cooling_generation_systems').initial_investment[0] - capital_cost_distribution_equipment = peak_cooling * chapter.item('D3040_distribution_systems').initial_investment[0] - capital_cost_other_hvac_ahu = peak_cooling * chapter.item('D3080_other_hvac_ahu').initial_investment[0] - capital_cost_lighting = self._total_floor_area * chapter.item('D5020_lighting_and_branch_wiring').initial_investment[0] - self._yearly_capital_costs.loc[0]['D301010_photovoltaic_system'] = capital_cost_pv - self._yearly_capital_costs.loc[0, 'D3020_heat_generating_systems'] = capital_cost_heating_equipment * own_capital - self._yearly_capital_costs.loc[0, 'D3030_cooling_generation_systems'] = capital_cost_cooling_equipment * own_capital - self._yearly_capital_costs.loc[0, 'D3040_distribution_systems'] = capital_cost_distribution_equipment * own_capital - self._yearly_capital_costs.loc[0, 'D3080_other_hvac_ahu'] = capital_cost_other_hvac_ahu * own_capital - self._yearly_capital_costs.loc[0, 'D5020_lighting_and_branch_wiring'] = capital_cost_lighting * own_capital + chapter = self._capital_costs_chapter.chapter('B_shell') + capital_cost_opaque = surface_opaque * chapter.item('B2010_opaque_walls').refurbishment[0] + capital_cost_transparent = surface_transparent * chapter.item('B2020_transparent').refurbishment[0] + capital_cost_roof = surface_roof * chapter.item('B3010_opaque_roof').refurbishment[0] + capital_cost_ground = surface_ground * chapter.item('B1010_superstructure').refurbishment[0] + self._yearly_capital_costs.loc[0, 'B2010_opaque_walls'] = capital_cost_opaque * self._own_capital + self._yearly_capital_costs.loc[0, 'B2020_transparent'] = capital_cost_transparent * self._own_capital + self._yearly_capital_costs.loc[0, 'B3010_opaque_roof'] = capital_cost_roof * self._own_capital + self._yearly_capital_costs.loc[0, 'B1010_superstructure'] = capital_cost_ground * self._own_capital + capital_cost_skin = capital_cost_opaque + capital_cost_ground + capital_cost_transparent + capital_cost_roof + return capital_cost_opaque, capital_cost_transparent, capital_cost_roof, capital_cost_ground, capital_cost_skin + def skin_yearly_capital_costs(self): + skin_capital_cost = self.skin_capital_cost() for year in range(1, self._configuration.number_of_years): - chapter = self._capital_costs_chapter.chapter('D_services') - costs_increase = math.pow(1 + self._configuration.consumer_price_index, year) self._yearly_capital_costs.loc[year, 'B2010_opaque_walls'] = ( -npf.pmt( self._configuration.interest_rate, self._configuration.credit_years, - capital_cost_opaque * self._configuration.percentage_credit + skin_capital_cost[0] * self._configuration.percentage_credit ) ) self._yearly_capital_costs.loc[year, 'B2020_transparent'] = ( -npf.pmt( self._configuration.interest_rate, self._configuration.credit_years, - capital_cost_transparent * self._configuration.percentage_credit + skin_capital_cost[1] * self._configuration.percentage_credit ) ) self._yearly_capital_costs.loc[year, 'B3010_opaque_roof'] = ( -npf.pmt( self._configuration.interest_rate, self._configuration.credit_years, - capital_cost_roof * self._configuration.percentage_credit + skin_capital_cost[2] * self._configuration.percentage_credit ) ) - self._yearly_capital_costs.loc[year, 'B10_superstructure'] = ( + self._yearly_capital_costs.loc[year, 'B1010_superstructure'] = ( -npf.pmt( self._configuration.interest_rate, self._configuration.credit_years, - capital_cost_ground * self._configuration.percentage_credit + skin_capital_cost[3] * self._configuration.percentage_credit ) ) - self._yearly_capital_costs.loc[year, 'D3020_heat_generating_systems'] = ( + + def energy_system_capital_cost(self): + chapter = self._capital_costs_chapter.chapter('D_services') + energy_system_components = self.system_components() + system_components = energy_system_components[0] + component_categories = energy_system_components[1] + component_sizes = energy_system_components[-1] + capital_cost_heating_and_cooling_equipment = 0 + capital_cost_domestic_hot_water_equipment = 0 + capital_cost_energy_storage_equipment = 0 + capital_cost_distribution_equipment = 0 + capital_cost_lighting = 0 + capital_cost_pv = self._surface_pv * chapter.item('D2010_photovoltaic_system').initial_investment[0] + # capital_cost_lighting = self._total_floor_area * \ + # chapter.item('D5020_lighting_and_branch_wiring').initial_investment[0] + for (i, component) in enumerate(system_components): + if component_categories[i] == 'generation': + capital_cost_heating_and_cooling_equipment += chapter.item(component).initial_investment[0] * component_sizes[i] + elif component_categories[i] == 'dhw': + capital_cost_domestic_hot_water_equipment += chapter.item(component).initial_investment[0] * \ + component_sizes[i] + elif component_categories[i] == 'distribution': + capital_cost_distribution_equipment += chapter.item(component).initial_investment[0] * \ + component_sizes[i] + else: + capital_cost_energy_storage_equipment += chapter.item(component).initial_investment[0] * component_sizes[i] + + self._yearly_capital_costs.loc[0, 'D2010_photovoltaic_system'] = capital_cost_pv + self._yearly_capital_costs.loc[0, 'D3020_heat_and_cooling_generating_systems'] = ( + capital_cost_heating_and_cooling_equipment * self._own_capital) + self._yearly_capital_costs.loc[0, 'D3040_distribution_systems'] = ( + capital_cost_distribution_equipment * self._own_capital) + self._yearly_capital_costs.loc[0, 'D3060_storage_systems'] = ( + capital_cost_energy_storage_equipment * self._own_capital) + self._yearly_capital_costs.loc[0, 'D40_dhw'] = ( + capital_cost_domestic_hot_water_equipment * self._own_capital) + # self._yearly_capital_costs.loc[0, 'D5020_lighting_and_branch_wiring'] = capital_cost_lighting * self._own_capital + capital_cost_hvac = capital_cost_heating_and_cooling_equipment + capital_cost_distribution_equipment + capital_cost_energy_storage_equipment + capital_cost_domestic_hot_water_equipment + return (capital_cost_pv, capital_cost_heating_and_cooling_equipment, capital_cost_distribution_equipment, + capital_cost_energy_storage_equipment, capital_cost_domestic_hot_water_equipment, capital_cost_lighting, capital_cost_hvac) + + def yearly_energy_system_costs(self): + chapter = self._capital_costs_chapter.chapter('D_services') + system_investment_costs = self.energy_system_capital_cost() + system_components = self.system_components()[0] + component_categories = self.system_components()[1] + component_sizes = self.system_components()[2] + for year in range(1, self._configuration.number_of_years): + costs_increase = math.pow(1 + self._configuration.consumer_price_index, year) + self._yearly_capital_costs.loc[year, 'D2010_photovoltaic_system'] = ( -npf.pmt( self._configuration.interest_rate, self._configuration.credit_years, - capital_cost_heating_equipment * self._configuration.percentage_credit + system_investment_costs[0] * self._configuration.percentage_credit ) ) - self._yearly_capital_costs.loc[year, 'D3030_cooling_generation_systems'] = ( + self._yearly_capital_costs.loc[year, 'D3020_heat_and_cooling_generating_systems'] = ( -npf.pmt( self._configuration.interest_rate, self._configuration.credit_years, - capital_cost_cooling_equipment * self._configuration.percentage_credit + system_investment_costs[1] * self._configuration.percentage_credit ) ) self._yearly_capital_costs.loc[year, 'D3040_distribution_systems'] = ( -npf.pmt( self._configuration.interest_rate, self._configuration.credit_years, - capital_cost_distribution_equipment * self._configuration.percentage_credit + system_investment_costs[2] * self._configuration.percentage_credit ) ) - self._yearly_capital_costs.loc[year, 'D3080_other_hvac_ahu'] = ( + self._yearly_capital_costs.loc[year, 'D3060_storage_systems'] = ( -npf.pmt( self._configuration.interest_rate, self._configuration.credit_years, - capital_cost_other_hvac_ahu * self._configuration.percentage_credit + system_investment_costs[3] * self._configuration.percentage_credit ) ) - self._yearly_capital_costs.loc[year, 'D5020_lighting_and_branch_wiring'] = ( + self._yearly_capital_costs.loc[year, 'D40_dhw'] = ( -npf.pmt( self._configuration.interest_rate, self._configuration.credit_years, - capital_cost_lighting * self._configuration.percentage_credit + system_investment_costs[4] * self._configuration.percentage_credit ) ) - - if (year % chapter.item('D3020_heat_generating_systems').lifetime) == 0: - reposition_cost_heating_equipment = ( - peak_heating * chapter.item('D3020_heat_generating_systems').reposition[0] * costs_increase - ) - self._yearly_capital_costs.loc[year, 'D3020_heat_generating_systems'] += reposition_cost_heating_equipment - - if (year % chapter.item('D3030_cooling_generation_systems').lifetime) == 0: - reposition_cost_cooling_equipment = ( - peak_cooling * chapter.item('D3030_cooling_generation_systems').reposition[0] * costs_increase - ) - self._yearly_capital_costs.loc[year, 'D3030_cooling_generation_systems'] += reposition_cost_cooling_equipment - - if (year % chapter.item('D3080_other_hvac_ahu').lifetime) == 0: - reposition_cost_hvac_ahu = ( - peak_cooling * chapter.item('D3080_other_hvac_ahu').reposition[0] * costs_increase - ) - self._yearly_capital_costs.loc[year, 'D3080_other_hvac_ahu'] = reposition_cost_hvac_ahu - - if (year % chapter.item('D5020_lighting_and_branch_wiring').lifetime) == 0: - reposition_cost_lighting = ( - self._total_floor_area * chapter.item('D5020_lighting_and_branch_wiring').reposition[0] * costs_increase - ) - self._yearly_capital_costs.loc[year, 'D5020_lighting_and_branch_wiring'] += reposition_cost_lighting - + # self._yearly_capital_costs.loc[year, 'D5020_lighting_and_branch_wiring'] = ( + # -npf.pmt( + # self._configuration.interest_rate, + # self._configuration.credit_years, + # system_investment_costs[5] * self._configuration.percentage_credit + # ) + # ) + # if (year % chapter.item('D5020_lighting_and_branch_wiring').lifetime) == 0: + # reposition_cost_lighting = ( + # self._total_floor_area * chapter.item('D5020_lighting_and_branch_wiring').reposition[0] * costs_increase + # ) + # self._yearly_capital_costs.loc[year, 'D5020_lighting_and_branch_wiring'] += reposition_cost_lighting if self._configuration.retrofit_scenario in (SYSTEM_RETROFIT_AND_PV, SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV): - if (year % chapter.item('D301010_photovoltaic_system').lifetime) == 0: - self._yearly_capital_costs.loc[year]['D301010_photovoltaic_system'] += ( - surface_pv * chapter.item('D301010_photovoltaic_system').reposition[0] * costs_increase + if (year % chapter.item('D2010_photovoltaic_system').lifetime) == 0: + self._yearly_capital_costs.loc[year, 'D2010_photovoltaic_system'] += ( + self._surface_pv * chapter.item('D2010_photovoltaic_system').reposition[0] * costs_increase ) - capital_cost_skin = capital_cost_opaque + capital_cost_ground + capital_cost_transparent + capital_cost_roof - capital_cost_hvac = ( - capital_cost_heating_equipment + - capital_cost_cooling_equipment + - capital_cost_distribution_equipment + - capital_cost_other_hvac_ahu + capital_cost_lighting - ) + for (i, component) in enumerate(system_components): + if (year % chapter.item(component).lifetime) == 0 and year != (self._configuration.number_of_years - 1): + if component_categories[i] == 'generation': + reposition_cost_heating_and_cooling_equipment = chapter.item(component).reposition[0] * component_sizes[i] * costs_increase + self._yearly_capital_costs.loc[year, 'D3020_heat_and_cooling_generating_systems'] += reposition_cost_heating_and_cooling_equipment + elif component_categories[i] == 'dhw': + reposition_cost_domestic_hot_water_equipment = chapter.item(component).reposition[0] * component_sizes[i] * costs_increase + self._yearly_capital_costs.loc[year, 'D40_dhw'] += reposition_cost_domestic_hot_water_equipment + elif component_categories[i] == 'distribution': + reposition_cost_distribution_equipment = chapter.item(component).reposition[0] * component_sizes[i] * costs_increase + self._yearly_capital_costs.loc[year, 'D3040_distribution_systems'] += reposition_cost_distribution_equipment + else: + reposition_cost_energy_storage_equipment = chapter.item(component).initial_investment[0] * component_sizes[i] * costs_increase + self._yearly_capital_costs.loc[year, 'D3060_storage_systems'] += reposition_cost_energy_storage_equipment + + def system_components(self): + system_components = [] + component_categories = [] + sizes = [] + energy_systems = self._building.energy_systems + for energy_system in energy_systems: + demand_types = energy_system.demand_types + generation_systems = energy_system.generation_systems + distribution_systems = energy_system.distribution_systems + for generation_system in generation_systems: + if generation_system.system_type != cte.PHOTOVOLTAIC: + heating_capacity = generation_system.nominal_heat_output or 0 + cooling_capacity = generation_system.nominal_cooling_output or 0 + installed_capacity = max(heating_capacity, cooling_capacity) + if cte.DOMESTIC_HOT_WATER in demand_types and cte.HEATING not in demand_types: + component_categories.append('dhw') + sizes.append(installed_capacity) + if generation_system.system_type == cte.HEAT_PUMP: + system_components.append(self.heat_pump_type(generation_system, domestic_how_water=True)) + elif generation_system.system_type == cte.BOILER and generation_system.fuel_type == cte.ELECTRICITY: + system_components.append(self.boiler_type(generation_system)) + else: + system_components.append('D302010_template_heat') + elif cte.HEATING or cte.COOLING in demand_types: + component_categories.append('generation') + sizes.append(installed_capacity) + if generation_system.system_type == cte.HEAT_PUMP: + item_type = self.heat_pump_type(generation_system) + system_components.append(item_type) + elif generation_system.system_type == cte.BOILER: + item_type = self.boiler_type(generation_system) + system_components.append(item_type) + else: + if cte.COOLING in demand_types and cte.HEATING not in demand_types: + system_components.append('D302090_template_cooling') + else: + system_components.append('D302010_template_heat') + + if generation_system.energy_storage_systems is not None: + energy_storage_systems = generation_system.energy_storage_systems + for storage_system in energy_storage_systems: + if storage_system.type_energy_stored == 'thermal': + component_categories.append('thermal storage') + sizes.append(storage_system.volume or 0) + system_components.append('D306010_storage_tank') + if distribution_systems is not None: + for distribution_system in distribution_systems: + component_categories.append('distribution') + sizes.append(self._building.cooling_peak_load[cte.YEAR][0] / 3.6e6) + system_components.append('D3040_distribution_systems') + return system_components, component_categories, sizes + + def yearly_incomes(self): + capital_cost_skin = self.skin_capital_cost()[-1] + system_investment_cost = self.energy_system_capital_cost() + capital_cost_hvac = system_investment_cost[-1] + capital_cost_pv = system_investment_cost[0] self._yearly_capital_incomes.loc[0, 'Subsidies construction'] = ( capital_cost_skin * self._archetype.income.construction_subsidy/100 @@ -236,4 +324,41 @@ class CapitalCosts(CostBase): self._yearly_capital_incomes.loc[0, 'Subsidies HVAC'] = capital_cost_hvac * self._archetype.income.hvac_subsidy/100 self._yearly_capital_incomes.loc[0, 'Subsidies PV'] = capital_cost_pv * self._archetype.income.photovoltaic_subsidy/100 self._yearly_capital_incomes.fillna(0, inplace=True) - return self._yearly_capital_costs, self._yearly_capital_incomes + + @staticmethod + def heat_pump_type(generation_system, domestic_how_water=False): + source_medium = generation_system.source_medium + supply_medium = generation_system.supply_medium + if domestic_how_water: + heat_pump_item = 'D4010_hot_water_heat_pump' + else: + if source_medium == cte.AIR and supply_medium == cte.WATER: + heat_pump_item = 'D302020_air_to_water_heat_pump' + elif source_medium == cte.AIR and supply_medium == cte.AIR: + heat_pump_item = 'D302050_air_to_air_heat_pump' + elif source_medium == cte.GROUND and supply_medium == cte.WATER: + heat_pump_item = 'D302030_ground_to_water_heat_pump' + elif source_medium == cte.GROUND and supply_medium == cte.AIR: + heat_pump_item = 'D302100_ground_to_air_heat_pump' + elif source_medium == cte.WATER and supply_medium == cte.WATER: + heat_pump_item = 'D302040_water_to_water_heat_pump' + elif source_medium == cte.WATER and supply_medium == cte.AIR: + heat_pump_item = 'D302110_water_to_air_heat_pump' + else: + heat_pump_item = 'D302010_template_heat' + return heat_pump_item + + @staticmethod + def boiler_type(generation_system): + fuel = generation_system.fuel_type + if fuel == cte.ELECTRICITY: + boiler_item = 'D302080_electrical_boiler' + elif fuel == cte.GAS: + boiler_item = 'D302070_natural_gas_boiler' + else: + boiler_item = 'D302010_template_heat' + return boiler_item + + + + diff --git a/scripts/costs/cost.py b/scripts/costs/cost.py index 7204b508..b040f063 100644 --- a/scripts/costs/cost.py +++ b/scripts/costs/cost.py @@ -11,10 +11,14 @@ import pandas as pd import numpy_financial as npf from hub.city_model_structure.building import Building from hub.helpers.dictionaries import Dictionaries - -from costs.configuration import Configuration -from costs import CapitalCosts, EndOfLifeCosts, TotalMaintenanceCosts, TotalOperationalCosts, TotalOperationalIncomes -from costs.constants import CURRENT_STATUS +from scripts.costs.configuration import Configuration +from scripts.costs.capital_costs import CapitalCosts +from scripts.costs.end_of_life_costs import EndOfLifeCosts +from scripts.costs.total_maintenance_costs import TotalMaintenanceCosts +from scripts.costs.total_operational_costs import TotalOperationalCosts +from scripts.costs.total_operational_incomes import TotalOperationalIncomes +from scripts.costs.constants import CURRENT_STATUS, SKIN_RETROFIT, SYSTEM_RETROFIT_AND_PV, SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV +import hub.helpers.constants as cte class Cost: @@ -31,25 +35,23 @@ class Cost: consumer_price_index=0.04, electricity_peak_index=0.05, electricity_price_index=0.05, - gas_price_index=0.05, + fuel_price_index=0.05, discount_rate=0.03, retrofitting_year_construction=2020, - factories_handler='montreal_custom', + factories_handler='montreal_new', retrofit_scenario=CURRENT_STATUS, dictionary=None): if dictionary is None: dictionary = Dictionaries().hub_function_to_montreal_custom_costs_function self._building = building - fuel_type = 0 - if "gas" in building.energy_systems_archetype_name: - fuel_type = 1 + fuel_type = self._building.fuel_consumption_breakdown.keys() self._configuration = Configuration(number_of_years, percentage_credit, interest_rate, credit_years, consumer_price_index, electricity_peak_index, electricity_price_index, - gas_price_index, + fuel_price_index, discount_rate, retrofitting_year_construction, factories_handler, @@ -79,28 +81,37 @@ class Cost: global_operational_costs = TotalOperationalCosts(self._building, self._configuration).calculate() global_maintenance_costs = TotalMaintenanceCosts(self._building, self._configuration).calculate() global_operational_incomes = TotalOperationalIncomes(self._building, self._configuration).calculate() + df_capital_costs_skin = ( global_capital_costs['B2010_opaque_walls'] + global_capital_costs['B2020_transparent'] + global_capital_costs['B3010_opaque_roof'] + - global_capital_costs['B10_superstructure'] + global_capital_costs['B1010_superstructure'] ) df_capital_costs_systems = ( - global_capital_costs['D3020_heat_generating_systems'] + - global_capital_costs['D3030_cooling_generation_systems'] + - global_capital_costs['D3080_other_hvac_ahu'] + + global_capital_costs['D3020_heat_and_cooling_generating_systems'] + + global_capital_costs['D3040_distribution_systems'] + + global_capital_costs['D3050_other_hvac_ahu'] + + global_capital_costs['D3060_storage_systems'] + + global_capital_costs['D40_dhw'] + global_capital_costs['D5020_lighting_and_branch_wiring'] + - global_capital_costs['D301010_photovoltaic_system'] + global_capital_costs['D2010_photovoltaic_system'] ) df_end_of_life_costs = global_end_of_life_costs['End_of_life_costs'] - df_operational_costs = ( - global_operational_costs['Fixed_costs_electricity_peak'] + - global_operational_costs['Fixed_costs_electricity_monthly'] + - global_operational_costs['Variable_costs_electricity'] + - global_operational_costs['Fixed_costs_gas'] + - global_operational_costs['Variable_costs_gas'] - ) + operational_costs_list = [ + global_operational_costs['Fixed Costs Electricity Peak'], + global_operational_costs['Fixed Costs Electricity Monthly'], + global_operational_costs['Variable Costs Electricity'] + ] + additional_costs = [ + global_operational_costs[f'Fixed Costs {fuel}'] for fuel in + self._building.fuel_consumption_breakdown.keys() if fuel != cte.ELECTRICITY + ] + [ + global_operational_costs[f'Variable Costs {fuel}'] for fuel in + self._building.fuel_consumption_breakdown.keys() if fuel != cte.ELECTRICITY + ] + df_operational_costs = sum(operational_costs_list + additional_costs) df_maintenance_costs = ( global_maintenance_costs['Heating_maintenance'] + global_maintenance_costs['Cooling_maintenance'] + @@ -116,7 +127,7 @@ class Cost: life_cycle_costs_capital_skin = self._npv_from_list(df_capital_costs_skin.values.tolist()) life_cycle_costs_capital_systems = self._npv_from_list(df_capital_costs_systems.values.tolist()) life_cycle_costs_end_of_life_costs = self._npv_from_list(df_end_of_life_costs.values.tolist()) - life_cycle_operational_costs = self._npv_from_list(df_operational_costs.values.tolist()) + life_cycle_operational_costs = self._npv_from_list([df_operational_costs]) life_cycle_maintenance_costs = self._npv_from_list(df_maintenance_costs.values.tolist()) life_cycle_operational_incomes = self._npv_from_list(df_operational_incomes.values.tolist()) life_cycle_capital_incomes = self._npv_from_list(df_capital_incomes.values.tolist()) diff --git a/scripts/costs/end_of_life_costs.py b/scripts/costs/end_of_life_costs.py index f7d0d63f..8dacfecc 100644 --- a/scripts/costs/end_of_life_costs.py +++ b/scripts/costs/end_of_life_costs.py @@ -9,8 +9,8 @@ import math import pandas as pd from hub.city_model_structure.building import Building -from costs.configuration import Configuration -from costs.cost_base import CostBase +from scripts.costs.configuration import Configuration +from scripts.costs.cost_base import CostBase class EndOfLifeCosts(CostBase): diff --git a/scripts/costs/total_maintenance_costs.py b/scripts/costs/total_maintenance_costs.py index 081df443..13cdc3a0 100644 --- a/scripts/costs/total_maintenance_costs.py +++ b/scripts/costs/total_maintenance_costs.py @@ -43,8 +43,8 @@ class TotalMaintenanceCosts(CostBase): roof_area += roof.solid_polygon.area surface_pv = roof_area * 0.5 - peak_heating = building.heating_peak_load[cte.YEAR][0] - peak_cooling = building.cooling_peak_load[cte.YEAR][0] + peak_heating = building.heating_peak_load[cte.YEAR][0] / 3.6e6 + peak_cooling = building.cooling_peak_load[cte.YEAR][0] / 3.6e6 maintenance_heating_0 = peak_heating * archetype.operational_cost.maintenance_heating maintenance_cooling_0 = peak_cooling * archetype.operational_cost.maintenance_cooling diff --git a/scripts/costs/total_operational_costs.py b/scripts/costs/total_operational_costs.py index e38ac5f5..338baab1 100644 --- a/scripts/costs/total_operational_costs.py +++ b/scripts/costs/total_operational_costs.py @@ -1,8 +1,7 @@ """ Total operational costs module SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2023 Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca -Code contributor Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +Copyright © 2024 Project Coder Saeed Ranjbar saeed.ranjbar@mail.concordia.ca Code contributor Oriol Gavalda Torrellas oriol.gavalda@concordia.ca """ import math @@ -11,27 +10,22 @@ import pandas as pd from hub.city_model_structure.building import Building import hub.helpers.constants as cte -from costs.configuration import Configuration -from costs.cost_base import CostBase -from costs.peak_load import PeakLoad +from scripts.costs.configuration import Configuration +from scripts.costs.cost_base import CostBase +from scripts.costs.peak_load import PeakLoad class TotalOperationalCosts(CostBase): """ - End of life costs class + Total Operational costs class """ def __init__(self, building: Building, configuration: Configuration): super().__init__(building, configuration) + columns_list = self.columns() self._yearly_operational_costs = pd.DataFrame( index=self._rng, - columns=[ - 'Fixed_costs_electricity_peak', - 'Fixed_costs_electricity_monthly', - 'Variable_costs_electricity', - 'Fixed_costs_gas', - 'Variable_costs_gas' - ], + columns=columns_list, dtype='float' ) @@ -41,67 +35,70 @@ class TotalOperationalCosts(CostBase): :return: pd.DataFrame """ building = self._building + fuel_consumption_breakdown = building.fuel_consumption_breakdown archetype = self._archetype total_floor_area = self._total_floor_area - factor_residential = total_floor_area / 80 - # todo: split the heating between fuels - fixed_gas_cost_year_0 = 0 - variable_gas_cost_year_0 = 0 - electricity_heating = 0 - domestic_hot_water_electricity = 0 - # todo: each fuel has different units that have to be processed - if self._configuration.fuel_type == 1: - fixed_gas_cost_year_0 = archetype.operational_cost.fuels[1].fixed_monthly * 12 * factor_residential - variable_gas_cost_year_0 = ( - (building.heating_consumption[cte.YEAR][0] + building.domestic_hot_water_consumption[cte.YEAR][0]) - / (1000 * cte.WATTS_HOUR_TO_JULES) * archetype.operational_cost.fuels[1].variable[0] - ) - if self._configuration.fuel_type == 0: - electricity_heating = building.heating_consumption[cte.YEAR][0] / 1000 - domestic_hot_water_electricity = building.domestic_hot_water_consumption[cte.YEAR][0] / 1000 - - electricity_cooling = building.cooling_consumption[cte.YEAR][0] / 1000 - electricity_lighting = building.lighting_electrical_demand[cte.YEAR][0] / 1000 - electricity_plug_loads = building.appliances_electrical_demand[cte.YEAR][0] / 1000 - electricity_distribution = 0 - total_electricity_consumption = ( - electricity_heating + electricity_cooling + electricity_lighting + domestic_hot_water_electricity + - electricity_plug_loads + electricity_distribution - ) - - # todo: change when peak electricity demand is coded. Careful with factor residential - peak_electricity_load = PeakLoad(building).electricity_peak_load + if archetype.function == 'residential': + factor = total_floor_area / 80 + else: + factor = 1 + total_electricity_consumption = sum(self._building.fuel_consumption_breakdown[cte.ELECTRICITY].values()) + peak_electricity_load = PeakLoad(self._building).electricity_peak_load peak_load_value = peak_electricity_load.max(axis=1) peak_electricity_demand = peak_load_value[1] / 1000 # self._peak_electricity_demand adapted to kW - variable_electricity_cost_year_0 = ( - total_electricity_consumption / cte.WATTS_HOUR_TO_JULES * archetype.operational_cost.fuels[0].variable[0] - ) - peak_electricity_cost_year_0 = peak_electricity_demand * archetype.operational_cost.fuels[0].fixed_power * 12 - monthly_electricity_cost_year_0 = archetype.operational_cost.fuels[0].fixed_monthly * 12 * factor_residential - - for year in range(1, self._configuration.number_of_years + 1): - price_increase_electricity = math.pow(1 + self._configuration.electricity_price_index, year) - price_increase_peak_electricity = math.pow(1 + self._configuration.electricity_peak_index, year) - price_increase_gas = math.pow(1 + self._configuration.gas_price_index, year) - self._yearly_operational_costs.at[year, 'Fixed_costs_electricity_peak'] = ( - peak_electricity_cost_year_0 * price_increase_peak_electricity - ) - self._yearly_operational_costs.at[year, 'Fixed_costs_electricity_monthly'] = ( - monthly_electricity_cost_year_0 * price_increase_peak_electricity - ) - if not isinstance(variable_electricity_cost_year_0, pd.DataFrame): - variable_costs_electricity = variable_electricity_cost_year_0 * price_increase_electricity - else: - variable_costs_electricity = float(variable_electricity_cost_year_0.iloc[0] * price_increase_electricity) - self._yearly_operational_costs.at[year, 'Variable_costs_electricity'] = ( - variable_costs_electricity - ) - self._yearly_operational_costs.at[year, 'Fixed_costs_gas'] = fixed_gas_cost_year_0 * price_increase_gas - self._yearly_operational_costs.at[year, 'Variable_costs_gas'] = ( - variable_gas_cost_year_0 * price_increase_peak_electricity - ) - self._yearly_operational_costs.at[year, 'Variable_costs_gas'] = ( - variable_gas_cost_year_0 * price_increase_peak_electricity - ) + fuels = archetype.operational_cost.fuels + for fuel in fuels: + if fuel.type in fuel_consumption_breakdown.keys(): + if fuel.type == cte.ELECTRICITY: + variable_electricity_cost_year_0 = ( + total_electricity_consumption / cte.WATTS_HOUR_TO_JULES * fuel.variable[0] + ) + peak_electricity_cost_year_0 = peak_electricity_demand * fuel.fixed_power * 12 + monthly_electricity_cost_year_0 = fuel.fixed_monthly * 12 * factor + for year in range(1, self._configuration.number_of_years + 1): + price_increase_electricity = math.pow(1 + self._configuration.electricity_price_index, year) + price_increase_peak_electricity = math.pow(1 + self._configuration.electricity_peak_index, year) + self._yearly_operational_costs.at[year, 'Fixed Costs Electricity Peak'] = ( + peak_electricity_cost_year_0 * price_increase_peak_electricity + ) + self._yearly_operational_costs.at[year, 'Fixed Costs Electricity Monthly'] = ( + monthly_electricity_cost_year_0 * price_increase_peak_electricity + ) + if not isinstance(variable_electricity_cost_year_0, pd.DataFrame): + variable_costs_electricity = variable_electricity_cost_year_0 * price_increase_electricity + else: + variable_costs_electricity = float(variable_electricity_cost_year_0.iloc[0] * price_increase_electricity) + self._yearly_operational_costs.at[year, 'Variable Costs Electricity'] = ( + variable_costs_electricity + ) + else: + fuel_fixed_cost = fuel.fixed_monthly * 12 * factor + if fuel.type == cte.BIOMASS: + conversion_factor = 1 + else: + conversion_factor = fuel.density[0] + variable_cost_fuel = ( + (sum(fuel_consumption_breakdown[fuel.type].values())/(1e6*fuel.lower_heating_value[0] * conversion_factor)) * fuel.variable[0]) + for year in range(1, self._configuration.number_of_years + 1): + price_increase_gas = math.pow(1 + self._configuration.gas_price_index, year) + self._yearly_operational_costs.at[year, f'Fixed Costs {fuel.type}'] = fuel_fixed_cost * price_increase_gas + self._yearly_operational_costs.at[year, f'Variable Costs {fuel.type}'] = ( + variable_cost_fuel * price_increase_gas) self._yearly_operational_costs.fillna(0, inplace=True) + return self._yearly_operational_costs + + def columns(self): + columns_list = [] + fuels = [key for key in self._building.fuel_consumption_breakdown.keys()] + for fuel in fuels: + if fuel == cte.ELECTRICITY: + columns_list.append('Fixed Costs Electricity Peak') + columns_list.append('Fixed Costs Electricity Monthly') + columns_list.append('Variable Costs Electricity') + else: + columns_list.append(f'Fixed Costs {fuel}') + columns_list.append(f'Variable Costs {fuel}') + + return columns_list + diff --git a/scripts/energy_system_analysis_report.py b/scripts/energy_system_analysis_report.py index 09b88482..9f44a288 100644 --- a/scripts/energy_system_analysis_report.py +++ b/scripts/energy_system_analysis_report.py @@ -6,7 +6,6 @@ import matplotlib.colors as mcolors from matplotlib import cm from scripts.report_creation import LatexReport - class EnergySystemAnalysisReport: def __init__(self, city, output_path): self.city = city @@ -196,50 +195,48 @@ class EnergySystemAnalysisReport: self.report.add_table(table_data, caption=f'{building.name} Information', first_column_width=1.5) - def building_existing_system_info(self, building): - existing_archetype = building.energy_systems_archetype_name - fuels = [] - system_schematic = "-" - heating_system = "-" - cooling_system = "-" - dhw = "-" - electricity = "Grid" - hvac_ec = format((building.heating_consumption[cte.YEAR][0] + building.cooling_consumption[cte.YEAR][0]) / 3.6e9, - '.2f') - dhw_ec = format(building.domestic_hot_water_consumption[cte.YEAR][0] / 1e6, '.2f') - on_site_generation = "-" - yearly_operational_cost = "-" - life_cycle_cost = "-" - for energy_system in building.energy_systems: - if cte.HEATING and cte.DOMESTIC_HOT_WATER in energy_system.demand_types: - heating_system = energy_system.name - dhw = energy_system.name - elif cte.DOMESTIC_HOT_WATER in energy_system.demand_types: - dhw = energy_system.name - elif cte.HEATING in energy_system.demand_types: - heating_system = energy_system.name - elif cte.COOLING in energy_system.demand_types: - cooling_system = energy_system.name - for generation_system in energy_system.generation_systems: - fuels.append(generation_system.fuel_type) - if generation_system.system_type == cte.PHOTOVOLTAIC: - electricity = "Grid-tied PV" + def building_system_retrofit_results(self, building_name, current_system, new_system): + current_system_archetype = current_system[f'{building_name}']['Energy System Archetype'] + current_system_heating = current_system[f'{building_name}']['Heating System'] + current_system_cooling = current_system[f'{building_name}']['Cooling System'] + current_system_dhw = current_system[f'{building_name}']['DHW System'] + current_system_pv = current_system[f'{building_name}']['Photovoltaic System Capacity'] + current_system_heating_fuel = current_system[f'{building_name}']['Heating Fuel'] + current_system_hvac_consumption = current_system[f'{building_name}']['Yearly HVAC Energy Consumption (MWh)'] + current_system_dhw_consumption = current_system[f'{building_name}']['DHW Energy Consumption (MWH)'] + current_pv_production = current_system[f'{building_name}']['PV Yearly Production (kWh)'] + current_capital_cost = current_system[f'{building_name}']['Energy System Capital Cost (CAD)'] + current_operational = current_system[f'{building_name}']['Energy System Average Yearly Operational Cost (CAD)'] + current_lcc = current_system[f'{building_name}']['Energy System Life Cycle Cost (CAD)'] + new_system_archetype = new_system[f'{building_name}']['Energy System Archetype'] + new_system_heating = new_system[f'{building_name}']['Heating System'] + new_system_cooling = new_system[f'{building_name}']['Cooling System'] + new_system_dhw = new_system[f'{building_name}']['DHW System'] + new_system_pv = new_system[f'{building_name}']['Photovoltaic System Capacity'] + new_system_heating_fuel = new_system[f'{building_name}']['Heating Fuel'] + new_system_hvac_consumption = new_system[f'{building_name}']['Yearly HVAC Energy Consumption (MWh)'] + new_system_dhw_consumption = new_system[f'{building_name}']['DHW Energy Consumption (MWH)'] + new_pv_production = new_system[f'{building_name}']['PV Yearly Production (kWh)'] + new_capital_cost = new_system[f'{building_name}']['Energy System Capital Cost (CAD)'] + new_operational = new_system[f'{building_name}']['Energy System Average Yearly Operational Cost (CAD)'] + new_lcc = new_system[f'{building_name}']['Energy System Life Cycle Cost (CAD)'] energy_system_table_data = [ ["Detail", "Existing System", "Proposed System"], - ["Energy System Archetype", existing_archetype, "-"], - ["System Schematic", system_schematic, system_schematic], - ["Heating System", heating_system, "-"], - ["Cooling System", cooling_system, "-"], - ["DHW System", dhw, "-"], - ["Electricity", electricity, "-"], - ["Fuel(s)", str(fuels), "-"], - ["HVAC Energy Consumption (MWh)", hvac_ec, "-"], - ["DHW Energy Consumption (MWH)", dhw_ec, "-"], - ["Yearly Operational Cost (CAD)", yearly_operational_cost, "-"], - ["Life Cycle Cost (CAD)", life_cycle_cost, "-"] + ["Energy System Archetype", current_system_archetype, new_system_archetype], + ["Heating System", current_system_heating, new_system_heating], + ["Cooling System", current_system_cooling, new_system_cooling], + ["DHW System", current_system_dhw, new_system_dhw], + ["Photovoltaic System Capacity", current_system_pv, new_system_pv], + ["Heating Fuel", current_system_heating_fuel, new_system_heating_fuel], + ["Yearly HVAC Energy Consumption (MWh)", current_system_hvac_consumption, new_system_hvac_consumption], + ["DHW Energy Consumption (MWH)", current_system_dhw_consumption, new_system_dhw_consumption], + ["PV Yearly Production (kWh)", current_pv_production, new_pv_production], + ["Energy System Capital Cost (CAD)", current_capital_cost, new_capital_cost], + ["Energy System Average Yearly Operational Cost (CAD)", current_operational, new_operational], + ["Energy System Life Cycle Cost (CAD)", current_lcc, new_lcc] ] - self.report.add_table(energy_system_table_data, caption=f'Building {building.name} Energy System Characteristics') + self.report.add_table(energy_system_table_data, caption=f'Building {building_name} Energy System Characteristics') def building_fuel_consumption_breakdown(self, building): save_directory = self.output_path @@ -255,19 +252,23 @@ class EnergySystemAnalysisReport: # Iterate through energy systems of the building for energy_system in building.energy_systems: for demand_type in energy_system.demand_types: - for generation_system in energy_system.generation_systems: - consumption = 0 - if demand_type == cte.HEATING: - consumption = building.heating_consumption[cte.YEAR][0] / 3.6e9 - elif demand_type == cte.DOMESTIC_HOT_WATER: - consumption = building.domestic_hot_water_consumption[cte.YEAR][0] / 1e6 - elif demand_type == cte.COOLING: - consumption = building.cooling_consumption[cte.YEAR][0] / 3.6e9 - - if generation_system.fuel_type == cte.ELECTRICITY: - fuel_breakdown[demand_type]["Electricity"] += consumption - else: - fuel_breakdown[demand_type]["Gas"] += consumption + if demand_type == cte.HEATING: + consumption = building.heating_consumption[cte.YEAR][0] / 3.6e9 + for generation_system in energy_system.generation_systems: + if generation_system.fuel_type == cte.ELECTRICITY: + fuel_breakdown[demand_type]["Electricity"] += consumption + else: + fuel_breakdown[demand_type]["Gas"] += consumption + elif demand_type == cte.DOMESTIC_HOT_WATER: + consumption = building.domestic_hot_water_consumption[cte.YEAR][0] / 1e6 + for generation_system in energy_system.generation_systems: + if generation_system.fuel_type == cte.ELECTRICITY: + fuel_breakdown[demand_type]["Electricity"] += consumption + else: + fuel_breakdown[demand_type]["Gas"] += consumption + elif demand_type == cte.COOLING: + consumption = building.cooling_consumption[cte.YEAR][0] / 3.6e9 + fuel_breakdown[demand_type]["Electricity"] += consumption electricity_labels = ['Appliance', 'Lighting'] electricity_sizes = [fuel_breakdown['Appliance'], fuel_breakdown['Lighting']] @@ -317,7 +318,7 @@ class EnergySystemAnalysisReport: plt.savefig(save_directory / f'{building.name}_energy_consumption_breakdown.png', dpi=300) plt.close() - def create_report(self): + def create_report(self, current_system, new_system): os.chdir(self.output_path) self.report.add_section('Current Status') self.building_energy_info() @@ -329,7 +330,7 @@ class EnergySystemAnalysisReport: self.load_duration_curves() for building in self.city.buildings: self.individual_building_info(building) - self.building_existing_system_info(building) + self.building_system_retrofit_results(building_name=building.name, current_system=current_system, new_system=new_system) self.building_fuel_consumption_breakdown(building) self.report.add_image(f'{building.name}_energy_consumption_breakdown.png', caption=f'Building {building.name} Consumption by source and sector breakdown') diff --git a/scripts/energy_system_retrofit_results.py b/scripts/energy_system_retrofit_results.py new file mode 100644 index 00000000..f3f1cd4a --- /dev/null +++ b/scripts/energy_system_retrofit_results.py @@ -0,0 +1,59 @@ +import hub.helpers.constants as cte + + +def system_results(buildings): + system_performance_summary = {} + fields = ["Energy System Archetype", "Heating System", "Cooling System", "DHW System", + "Photovoltaic System Capacity", "Heating Fuel", "Yearly HVAC Energy Consumption (MWh)", + "DHW Energy Consumption (MWH)", "PV Yearly Production (kWh)", + "Energy System Capital Cost (CAD)", "Energy System Average Yearly Operational Cost (CAD)", + "Energy System Life Cycle Cost (CAD)"] + for building in buildings: + system_performance_summary[f'{building.name}'] = {} + for field in fields: + system_performance_summary[f'{building.name}'][field] = '-' + + for building in buildings: + fuels = [] + system_performance_summary[f'{building.name}']['Energy System Archetype'] = building.energy_systems_archetype_name + energy_systems = building.energy_systems + for energy_system in energy_systems: + demand_types = energy_system.demand_types + if cte.HEATING and cte.DOMESTIC_HOT_WATER in demand_types: + system_performance_summary[f'{building.name}']['Heating System'] = energy_system.name + system_performance_summary[f'{building.name}']['DHW System'] = energy_system.name + for generation_system in energy_system.generation_systems: + fuels.append(generation_system.fuel_type) + elif cte.DOMESTIC_HOT_WATER in energy_system.demand_types: + system_performance_summary[f'{building.name}']['DHW System'] = energy_system.name + elif cte.HEATING in energy_system.demand_types: + system_performance_summary[f'{building.name}']['Heating System'] = energy_system.name + for generation_system in energy_system.generation_systems: + fuels.append(generation_system.fuel_type) + elif cte.COOLING in energy_system.demand_types: + system_performance_summary[f'{building.name}']['Cooling System'] = energy_system.name + for generation_system in energy_system.generation_systems: + if generation_system.system_type == cte.PHOTOVOLTAIC: + system_performance_summary[f'{building.name}'][ + 'Photovoltaic System Capacity'] = generation_system.nominal_electricity_output or str(0) + heating_fuels = ", ".join(fuels) + system_performance_summary[f'{building.name}']['Heating Fuel'] = heating_fuels + system_performance_summary[f'{building.name}']['Yearly HVAC Energy Consumption (MWh)'] = format( + (building.heating_consumption[cte.YEAR][0] + building.cooling_consumption[cte.YEAR][0]) / 3.6e9, '.2f') + system_performance_summary[f'{building.name}']['DHW Energy Consumption (MWH)'] = format( + building.domestic_hot_water_consumption[cte.YEAR][0] / 1e6, '.2f') + return system_performance_summary + + +def new_system_results(buildings): + new_system_performance_summary = {} + fields = ["Energy System Archetype", "Heating System", "Cooling System", "DHW System", + "Photovoltaic System Capacity", "Heating Fuel", "Yearly HVAC Energy Consumption (MWh)", + "DHW Energy Consumption (MWH)", "PV Yearly Production (kWh)", + "Energy System Capital Cost (CAD)", "Energy System Average Yearly Operational Cost (CAD)", + "Energy System Life Cycle Cost (CAD)"] + for building in buildings: + new_system_performance_summary[f'{building.name}'] = {} + for field in fields: + new_system_performance_summary[f'{building.name}'][field] = '-' + return new_system_performance_summary diff --git a/scripts/energy_system_sizing.py b/scripts/energy_system_sizing.py index 42b53886..7f49d6af 100644 --- a/scripts/energy_system_sizing.py +++ b/scripts/energy_system_sizing.py @@ -17,18 +17,21 @@ class SystemSizing: def hvac_sizing(self): for building in self.buildings: - peak_heating_demand = building.heating_peak_load[cte.YEAR][0] - peak_cooling_demand = building.cooling_peak_load[cte.YEAR][0] + peak_heating_demand = building.heating_peak_load[cte.YEAR][0] / 3600 + peak_cooling_demand = building.cooling_peak_load[cte.YEAR][0] / 3600 if peak_heating_demand > peak_cooling_demand: sizing_demand = peak_heating_demand for system in building.energy_systems: if 'Heating' in system.demand_types: for generation in system.generation_systems: if generation.system_type == 'Heat Pump': + if generation.source_medium == cte.AIR: + generation.source_temperature = building.external_temperature generation.nominal_heat_output = 0.6 * sizing_demand / 1000 - for storage in generation.energy_storage_systems: - if storage.type_energy_stored == 'thermal': - storage.volume = (sizing_demand * 1000) / (cte.WATER_HEAT_CAPACITY*cte.WATER_DENSITY) + if generation.energy_storage_systems is not None: + for storage in generation.energy_storage_systems: + if storage.type_energy_stored == 'thermal': + storage.volume = (sizing_demand * 1000) / (cte.WATER_HEAT_CAPACITY*cte.WATER_DENSITY) elif generation.system_type == 'Boiler': generation.nominal_heat_output = 0.4 * sizing_demand / 1000 @@ -40,6 +43,27 @@ class SystemSizing: if generation.system_type == 'Heat Pump': generation.nominal_heat_output = sizing_demand / 1000 + def montreal_custom(self): + for building in self.buildings: + energy_systems = building.energy_systems + for energy_system in energy_systems: + demand_types = energy_system.demand_types + generation_systems = energy_system.generation_systems + if cte.HEATING in demand_types: + if len(generation_systems) == 1: + for generation in generation_systems: + generation.nominal_heat_output = building.heating_peak_load[cte.YEAR][0] / 3.6e6 + else: + for generation in generation_systems: + generation.nominal_heat_output = building.heating_peak_load[cte.YEAR][0] / (len(generation_systems) * 3.6e6) + elif cte.COOLING in demand_types: + if len(generation_systems) == 1: + for generation in generation_systems: + generation.nominal_cooling_output = building.cooling_peak_load[cte.YEAR][0] / 3.6e6 + else: + for generation in generation_systems: + generation.nominal_heat_output = building.cooling_peak_load[cte.YEAR][0] / (len(generation_systems) * 3.6e6) + diff --git a/scripts/energy_system_sizing_and_simulation_factory.py b/scripts/energy_system_sizing_and_simulation_factory.py new file mode 100644 index 00000000..adef8151 --- /dev/null +++ b/scripts/energy_system_sizing_and_simulation_factory.py @@ -0,0 +1,33 @@ +""" +EnergySystemSizingSimulationFactory retrieve the energy system archetype sizing and simulation module +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Saeed Ranjbar saeed.ranjbar@mail.concordia.ca +""" + +from scripts.system_simulation_models.archetype13 import Archetype13 + + +class EnergySystemsSimulationFactory: + """ + EnergySystemsFactory class + """ + + def __init__(self, handler, building): + self._handler = '_' + handler.lower() + self._building = building + + def _archetype13(self): + """ + Enrich the city by using the sizing and simulation model developed for archetype13 of montreal_future_systems + """ + Archetype13(self._building).enrich_buildings() + self._building.level_of_detail.energy_systems = 2 + self._building.level_of_detail.energy_systems = 2 + + def enrich(self): + """ + Enrich the city given to the class using the class given handler + :return: None + """ + getattr(self, self._handler, lambda: None)() diff --git a/scripts/random_assignation.py b/scripts/random_assignation.py index 4c2b558a..d607c259 100644 --- a/scripts/random_assignation.py +++ b/scripts/random_assignation.py @@ -15,8 +15,8 @@ from hub.city_model_structure.building import Building energy_systems_format = 'montreal_custom' # parameters: -residential_systems_percentage = {'system 1 gas': 44, - 'system 1 electricity': 6, +residential_systems_percentage = {'system 1 gas': 100, + 'system 1 electricity': 0, 'system 2 gas': 0, 'system 2 electricity': 0, 'system 3 and 4 gas': 0, @@ -25,10 +25,11 @@ residential_systems_percentage = {'system 1 gas': 44, 'system 5 electricity': 0, 'system 6 gas': 0, 'system 6 electricity': 0, - 'system 8 gas': 44, - 'system 8 electricity': 6} + 'system 8 gas': 0, + 'system 8 electricity': 0} residential_new_systems_percentage = {'PV+ASHP+GasBoiler+TES': 100, + 'PV+4Pipe+DHW': 0, 'PV+ASHP+ElectricBoiler+TES': 0, 'PV+GSHP+GasBoiler+TES': 0, 'PV+GSHP+ElectricBoiler+TES': 0, diff --git a/scripts/system_simulation.py b/scripts/system_simulation.py index 8fbe9148..24759f26 100644 --- a/scripts/system_simulation.py +++ b/scripts/system_simulation.py @@ -9,10 +9,10 @@ from hub.helpers.monthly_values import MonthlyValues class SystemSimulation: - def __init__(self, building): + def __init__(self, building, out_path): self.building = building self.energy_systems = building.energy_systems - self.heating_demand = building.heating_demand[cte.HOUR] + self.heating_demand = [0] + building.heating_demand[cte.HOUR] self.cooling_demand = building.cooling_demand self.dhw_demand = building.domestic_hot_water_heat_demand self.T_out = building.external_temperature[cte.HOUR] @@ -20,9 +20,10 @@ class SystemSimulation: self.maximum_cooling_demand = building.cooling_peak_load[cte.YEAR][0] self.name = building.name self.energy_system_archetype = building.energy_systems_archetype_name + self.out_path = out_path def archetype1(self): - out_path = (Path(__file__).parent / 'out_files') + out_path = self.out_path T, T_sup, T_ret, m_ch, m_dis, q_hp, q_aux = [0] * len(self.heating_demand), [0] * len( self.heating_demand), [0] * len(self.heating_demand), [0] * len(self.heating_demand), [0] * len( self.heating_demand), [0] * len(self.heating_demand), [0] * len(self.heating_demand) @@ -36,7 +37,7 @@ class SystemSimulation: if cte.ELECTRICITY not in energy_system.demand_types: generation_systems = energy_system.generation_systems for generation_system in generation_systems: - if generation_system.system_type == cte.HEAT_PUMP: + if generation_system.system_type == cte.HEAT_PUMP and cte.HEATING in energy_system.demand_types: hp_cap = generation_system.nominal_heat_output hp_efficiency = float(generation_system.heat_efficiency) for storage in generation_system.energy_storage_systems: @@ -79,15 +80,15 @@ class SystemSimulation: factor = 8 else: factor = 4 - m_dis[i + 1] = self.maximum_heating_demand / (cte.WATER_HEAT_CAPACITY * factor) + m_dis[i + 1] = self.maximum_heating_demand / (cte.WATER_HEAT_CAPACITY * factor * 3600) t_return = T[i + 1] - self.heating_demand[i + 1] / (m_dis[i + 1] * cte.WATER_HEAT_CAPACITY) if m_dis[i + 1] == 0 or (m_dis[i + 1] > 0 and t_return < 25): T_ret[i + 1] = max(25, T[i + 1]) else: - T_ret[i + 1] = T[i + 1] - self.heating_demand[i + 1] / (m_dis[i + 1] * cte.WATER_HEAT_CAPACITY) + T_ret[i + 1] = T[i + 1] - self.heating_demand[i + 1] / (m_dis[i + 1] * cte.WATER_HEAT_CAPACITY * 3600) tes_output = m_dis[i + 1] * cte.WATER_HEAT_CAPACITY * (T[i + 1] - T_ret[i + 1]) - if tes_output < self.heating_demand[i + 1]: - q_aux[i + 1] = self.heating_demand[i + 1] - tes_output + if tes_output < (self.heating_demand[i + 1] / 3600): + q_aux[i + 1] = (self.heating_demand[i + 1] / 3600) - tes_output aux_fuel[i + 1] = (q_aux[i + 1] * dt) / 50e6 boiler_consumption[i + 1] = q_aux[i + 1] / boiler_efficiency heating_consumption[i + 1] = boiler_consumption[i + 1] + hp_electricity[i + 1] @@ -99,11 +100,11 @@ class SystemSimulation: output_file.writerow(['T', 'T_sup', 'T_ret', 'm_ch', 'm_dis', 'q_hp', 'hp_electricity', 'aux_fuel', 'q_aux', 'heating_demand']) # Write data output_file.writerows(data) - return heating_consumption, hp_electricity, boiler_consumption + return heating_consumption, hp_electricity, boiler_consumption, T_sup def enrich(self): - if self.energy_system_archetype == 'PV+ASHP+GasBoiler+TES': - building_new_heating_consumption, building_heating_electricity_consumption, building_heating_gas_consumption = ( + if self.energy_system_archetype == 'PV+ASHP+GasBoiler+TES' or 'PV+4Pipe+DHW': + building_new_heating_consumption, building_heating_electricity_consumption, building_heating_gas_consumption, supply_temperature = ( self.archetype1()) self.building.heating_consumption[cte.HOUR] = building_new_heating_consumption self.building.heating_consumption[cte.MONTH] = MonthlyValues.get_total_month(self.building.heating_consumption[cte.HOUR]) @@ -112,6 +113,8 @@ class SystemSimulation: for energy_system in self.building.energy_systems: if cte.HEATING in energy_system.demand_types: for generation_system in energy_system.generation_systems: + if generation_system.system_type == cte.HEAT_PUMP: + generation_system.heat_supply_temperature = supply_temperature disaggregated_consumption[generation_system.fuel_type] = {} if generation_system.fuel_type == cte.ELECTRICITY: disaggregated_consumption[generation_system.fuel_type][ diff --git a/scripts/system_simulation_models/archetype13.py b/scripts/system_simulation_models/archetype13.py new file mode 100644 index 00000000..03057cdc --- /dev/null +++ b/scripts/system_simulation_models/archetype13.py @@ -0,0 +1,100 @@ +import math + +import hub.helpers.constants as cte + + +class Archetype13: + def __init__(self, building, output_path): + self._pv_system = building.energy_systems[0] + self._hvac_system = building.energy_systems[1] + self._dhw_system = building.energy_systems[-1] + self._heating_peak_load = building.heating_peak_load[cte.YEAR][0] + self._cooling_peak_load = building.cooling_peak_load[cte.YEAR][0] + self._domestic_hot_water_peak_load = building.domestic_hot_water_peak_load[cte.YEAR][0] + self._hourly_heating_demand = [0] + [demand / 3600 for demand in building.heating_demand[cte.HOUR]] + self._hourly_cooling_demand = [demand / 3600 for demand in building.cooling_demand[cte.HOUR]] + self._hourly_dhw_demand = building.domestic_hot_water_heat_demand[cte.HOUR] + self._output_path = output_path + self._t_out = building.external_temperature + + def hvac_sizing(self): + storage_factor = 3 + heat_pump = self._hvac_system.generation_systems[0] + boiler = self._hvac_system.generation_systems[1] + thermal_storage = heat_pump.energy_storage_systems[0] + heat_pump.nominal_heat_output = round(0.5 * self._heating_peak_load / 3600) + heat_pump.nominal_cooling_output = round(self._cooling_peak_load / 3600) + boiler.nominal_heat_output = round(0.5 * self._heating_peak_load / 3600) + thermal_storage.volume = round( + (self._heating_peak_load * storage_factor) / (cte.WATER_HEAT_CAPACITY * cte.WATER_DENSITY * 30)) + return heat_pump, boiler, thermal_storage + + def hvac_simulation(self): + hp, boiler, tes = self.hvac_sizing() + if hp.source_medium == cte.AIR: + hp.source_temperature = self._t_out[cte.HOUR] + # Heating System Simulation + variable_names = ["t_sup", "t_tank", "t_ret", "m_ch", "m_dis", "q_hp", "q_boiler", "hp_cop", + "hp_electricity", "boiler_gas", "boiler_consumption", "heating_consumption"] + num_hours = len(self._hourly_heating_demand) + variables = {name: [0] * num_hours for name in variable_names} + (t_sup, t_tank, t_ret, m_ch, m_dis, q_hp, q_boiler, hp_cop, + hp_electricity, boiler_gas, boiler_consumption, heating_consumption) = [variables[name] for name in variable_names] + t_tank[0] = 30 + dt = 3600 + hp_heating_cap = hp.nominal_heat_output + hp_efficiency = float(hp.heat_efficiency) + boiler_efficiency = float(boiler.heat_efficiency) + v, h = float(tes.volume), float(tes.height) + r_tot = sum(float(layer.thickness) / float(layer.material.conductivity) for layer in + tes.layers) + u_tot = 1 / r_tot + d = math.sqrt((4 * v) / (math.pi * h)) + a_side = math.pi * d * h + a_top = math.pi * d ** 2 / 4 + ua = u_tot * (2 * a_top + a_side) + for i in range(len(self._hourly_heating_demand) - 1): + t_tank[i + 1] = (t_tank[i] + + ((m_ch[i] * (t_sup[i] - t_tank[i])) + + (ua * (self._t_out[i] - t_tank[i] + 5)) / cte.WATER_HEAT_CAPACITY - + m_dis[i] * (t_tank[i] - t_ret[i])) * (dt / (cte.WATER_DENSITY * v))) + if t_tank[i + 1] < 40: + q_hp[i + 1] = hp_heating_cap + m_ch[i + 1] = q_hp[i + 1] / (cte.WATER_HEAT_CAPACITY * 7) + t_sup[i + 1] = (q_hp[i + 1] / (m_ch[i + 1] * cte.WATER_HEAT_CAPACITY)) + t_tank[i + 1] + elif 45 <= t_tank[i + 1] < 55 and q_hp[i] == 0: + q_hp[i + 1] = 0 + m_ch[i + 1] = 0 + t_sup[i + 1] = t_tank[i + 1] + elif 45 <= t_tank[i + 1] < 55 and q_hp[i] > 0: + q_hp[i + 1] = hp_heating_cap + m_ch[i + 1] = q_hp[i + 1] / (cte.WATER_HEAT_CAPACITY * 3) + t_sup[i + 1] = (q_hp[i + 1] / (m_ch[i + 1] * cte.WATER_HEAT_CAPACITY)) + t_tank[i + 1] + else: + q_hp[i + 1], m_ch[i + 1], t_sup[i + 1] = 0, 0, t_tank[i + 1] + + hp_electricity[i + 1] = q_hp[i + 1] / hp_efficiency + if self._hourly_heating_demand[i + 1] == 0: + m_dis[i + 1], t_return, t_ret[i + 1] = 0, t_tank[i + 1], t_tank[i + 1] + else: + if self._hourly_heating_demand[i + 1] > 0.5 * self._heating_peak_load: + factor = 8 + else: + factor = 4 + m_dis[i + 1] = self._heating_peak_load / (cte.WATER_HEAT_CAPACITY * factor * 3600) + t_return = t_tank[i + 1] - self._hourly_heating_demand[i + 1] / (m_dis[i + 1] * cte.WATER_HEAT_CAPACITY) + if m_dis[i + 1] == 0 or (m_dis[i + 1] > 0 and t_return < 25): + t_ret[i + 1] = max(25, t_tank[i + 1]) + else: + t_ret[i + 1] = t_tank[i + 1] - self._hourly_heating_demand[i + 1] / (m_dis[i + 1] * cte.WATER_HEAT_CAPACITY * 3600) + tes_output = m_dis[i + 1] * cte.WATER_HEAT_CAPACITY * (t_tank[i + 1] - t_ret[i + 1]) + if tes_output < (self._hourly_heating_demand[i + 1] / 3600): + q_boiler[i + 1] = (self._hourly_heating_demand[i + 1] / 3600) - tes_output + boiler_gas[i + 1] = (q_boiler[i + 1] * dt) / 50e6 + boiler_consumption[i + 1] = q_boiler[i + 1] / boiler_efficiency + heating_consumption[i + 1] = boiler_consumption[i + 1] + hp_electricity[i + 1] + data = list(zip(t_tank, t_sup, t_ret, m_ch, m_dis, q_hp, hp_electricity, boiler_gas, q_boiler, + self._hourly_heating_demand)) + + def enrich_buildings(self): + self.hvac_sizing() diff --git a/tests/__pycache__/test_systems_catalog.cpython-39.pyc b/tests/__pycache__/test_systems_catalog.cpython-39.pyc index 26324a1a..923b2ef7 100644 Binary files a/tests/__pycache__/test_systems_catalog.cpython-39.pyc and b/tests/__pycache__/test_systems_catalog.cpython-39.pyc differ diff --git a/tests/__pycache__/test_systems_factory.cpython-39.pyc b/tests/__pycache__/test_systems_factory.cpython-39.pyc index f6c71e1a..1458d3dc 100644 Binary files a/tests/__pycache__/test_systems_factory.cpython-39.pyc and b/tests/__pycache__/test_systems_factory.cpython-39.pyc differ diff --git a/tests/test_systems_catalog.py b/tests/test_systems_catalog.py index d41e5c07..839107c2 100644 --- a/tests/test_systems_catalog.py +++ b/tests/test_systems_catalog.py @@ -39,11 +39,11 @@ class TestSystemsCatalog(TestCase): catalog_categories = catalog.names() archetypes = catalog.names('archetypes') - self.assertEqual(12, len(archetypes['archetypes'])) + self.assertEqual(13, len(archetypes['archetypes'])) systems = catalog.names('systems') - self.assertEqual(7, len(systems['systems'])) + self.assertEqual(10, len(systems['systems'])) generation_equipments = catalog.names('generation_equipments') - self.assertEqual(26, len(generation_equipments['generation_equipments'])) + self.assertEqual(27, len(generation_equipments['generation_equipments'])) with self.assertRaises(ValueError): catalog.names('unknown') diff --git a/tests/test_systems_factory.py b/tests/test_systems_factory.py index c4c086e3..442e5be5 100644 --- a/tests/test_systems_factory.py +++ b/tests/test_systems_factory.py @@ -114,7 +114,7 @@ class TestSystemsFactory(TestCase): ResultFactory('insel_monthly_energy_balance', self._city, self._output_path).enrich() for building in self._city.buildings: - building.energy_systems_archetype_name = 'PV+ASHP+GasBoiler+TES' + building.energy_systems_archetype_name = 'PV+4Pipe+DHW' EnergySystemsFactory('montreal_future', self._city).enrich() # Need to assign energy systems to buildings: for building in self._city.buildings: