diff --git a/.idea/misc.xml b/.idea/misc.xml index dc7953c5..757b558a 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,7 @@ + + \ No newline at end of file diff --git a/energy_system_retrofit.py b/energy_system_retrofit.py new file mode 100644 index 00000000..b6b8cd31 --- /dev/null +++ b/energy_system_retrofit.py @@ -0,0 +1,98 @@ +from pathlib import Path +import subprocess +from scripts.ep_run_enrich import energy_plus_workflow +from hub.imports.geometry_factory import GeometryFactory +from hub.helpers.dictionaries import Dictionaries +from hub.imports.construction_factory import ConstructionFactory +from hub.imports.usage_factory import UsageFactory +from hub.imports.weather_factory import WeatherFactory +from hub.imports.results_factory import ResultFactory +from scripts.energy_system_retrofit_report import EnergySystemRetrofitReport +from scripts.geojson_creator import process_geojson +from scripts import random_assignation +from hub.imports.energy_systems_factory import EnergySystemsFactory +from scripts.energy_system_sizing import SystemSizing +from scripts.solar_angles import CitySolarAngles +from scripts.pv_sizing_and_simulation import PVSizingSimulation +from scripts.energy_system_retrofit_results import consumption_data, cost_data +from scripts.energy_system_sizing_and_simulation_factory import EnergySystemsSimulationFactory +from scripts.costs.cost import Cost +from scripts.costs.constants import SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV, SYSTEM_RETROFIT_AND_PV, CURRENT_STATUS +import hub.helpers.constants as cte +from hub.exports.exports_factory import ExportsFactory +from scripts.pv_feasibility import pv_feasibility + +# Specify the GeoJSON file path +input_files_path = (Path(__file__).parent / 'input_files') +input_files_path.mkdir(parents=True, exist_ok=True) +geojson_file = process_geojson(x=-73.5681295982132, y=45.49218262677643, diff=0.0001) +geojson_file_path = input_files_path / 'output_buildings.geojson' +output_path = (Path(__file__).parent / 'out_files').resolve() +output_path.mkdir(parents=True, exist_ok=True) +energy_plus_output_path = output_path / 'energy_plus_outputs' +energy_plus_output_path.mkdir(parents=True, exist_ok=True) +simulation_results_path = (Path(__file__).parent / 'out_files' / 'simulation_results').resolve() +simulation_results_path.mkdir(parents=True, exist_ok=True) +sra_output_path = output_path / 'sra_outputs' +sra_output_path.mkdir(parents=True, exist_ok=True) +cost_analysis_output_path = output_path / 'cost_analysis' +cost_analysis_output_path.mkdir(parents=True, exist_ok=True) +city = GeometryFactory(file_type='geojson', + path=geojson_file_path, + height_field='height', + year_of_construction_field='year_of_construction', + function_field='function', + function_to_hub=Dictionaries().montreal_function_to_hub_function).city +ConstructionFactory('nrcan', city).enrich() +UsageFactory('nrcan', city).enrich() +WeatherFactory('epw', city).enrich() +ExportsFactory('sra', city, sra_output_path).export() +sra_path = (sra_output_path / f'{city.name}_sra.xml').resolve() +subprocess.run(['sra', str(sra_path)]) +ResultFactory('sra', city, sra_output_path).enrich() +pv_feasibility(-73.5681295982132, 45.49218262677643, 0.0001, selected_buildings=city.buildings) +energy_plus_workflow(city, energy_plus_output_path) +solar_angles = CitySolarAngles(city.name, + city.latitude, + city.longitude, + tilt_angle=45, + surface_azimuth_angle=180).calculate +random_assignation.call_random(city.buildings, random_assignation.residential_systems_percentage) +EnergySystemsFactory('montreal_custom', city).enrich() +SystemSizing(city.buildings).montreal_custom() +current_status_energy_consumption = consumption_data(city) +current_status_life_cycle_cost = {} +for building in city.buildings: + cost_retrofit_scenario = CURRENT_STATUS + lcc_dataframe = Cost(building=building, + retrofit_scenario=cost_retrofit_scenario, + fuel_tariffs=['Electricity-D', 'Gas-Energir']).life_cycle + lcc_dataframe.to_csv(cost_analysis_output_path / f'{building.name}_current_status_lcc.csv') + current_status_life_cycle_cost[f'{building.name}'] = cost_data(building, lcc_dataframe, cost_retrofit_scenario) +random_assignation.call_random(city.buildings, random_assignation.residential_new_systems_percentage) +EnergySystemsFactory('montreal_future', city).enrich() +for building in city.buildings: + if 'PV' in building.energy_systems_archetype_name: + ghi = [x / cte.WATTS_HOUR_TO_JULES for x in building.roofs[0].global_irradiance[cte.HOUR]] + pv_sizing_simulation = PVSizingSimulation(building, + solar_angles, + tilt_angle=45, + module_height=1, + module_width=2, + ghi=ghi) + pv_sizing_simulation.pv_output() + if building.energy_systems_archetype_name == 'PV+4Pipe+DHW': + EnergySystemsSimulationFactory('archetype13', building=building, output_path=simulation_results_path).enrich() +retrofitted_energy_consumption = consumption_data(city) +retrofitted_life_cycle_cost = {} +for building in city.buildings: + cost_retrofit_scenario = SYSTEM_RETROFIT_AND_PV + lcc_dataframe = Cost(building=building, + retrofit_scenario=cost_retrofit_scenario, + fuel_tariffs=['Electricity-D', 'Gas-Energir']).life_cycle + lcc_dataframe.to_csv(cost_analysis_output_path / f'{building.name}_retrofitted_lcc.csv') + retrofitted_life_cycle_cost[f'{building.name}'] = cost_data(building, lcc_dataframe, cost_retrofit_scenario) +(EnergySystemRetrofitReport(city, output_path, 'PV Implementation and System Retrofit', + current_status_energy_consumption, retrofitted_energy_consumption, + current_status_life_cycle_cost, retrofitted_life_cycle_cost).create_report()) + diff --git a/hub/catalog_factories/cost/montreal_complete_cost_catalog.py b/hub/catalog_factories/cost/montreal_complete_cost_catalog.py index 7d6e8ade..0c51bfd2 100644 --- a/hub/catalog_factories/cost/montreal_complete_cost_catalog.py +++ b/hub/catalog_factories/cost/montreal_complete_cost_catalog.py @@ -6,6 +6,7 @@ Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ import xmltodict +import json from hub.catalog_factories.catalog import Catalog from hub.catalog_factories.data_models.cost.archetype import Archetype from hub.catalog_factories.data_models.cost.content import Content @@ -15,6 +16,7 @@ from hub.catalog_factories.data_models.cost.item_description import ItemDescript from hub.catalog_factories.data_models.cost.operational_cost import OperationalCost from hub.catalog_factories.data_models.cost.fuel import Fuel from hub.catalog_factories.data_models.cost.income import Income +from hub.catalog_factories.data_models.cost.pricing_rate import PricingRate class MontrealNewCatalog(Catalog): @@ -24,6 +26,7 @@ class MontrealNewCatalog(Catalog): def __init__(self, path): path = (path / 'montreal_costs_completed.xml').resolve() + self._fuel_rates_path = (path.parent / 'fuel_rates.json').resolve() with open(path, 'r', encoding='utf-8') as xml: self._archetypes = xmltodict.parse(xml.read(), force_list='archetype') @@ -45,7 +48,7 @@ class MontrealNewCatalog(Catalog): construction = float(archetype['incomes']['subsidies']['construction']['#text']) hvac = float(archetype['incomes']['subsidies']['hvac']['#text']) photovoltaic_system = float(archetype['incomes']['subsidies']['photovoltaic']['#text']) - electricity_exports = float(archetype['incomes']['electricity_export']['#text']) / 1000 / 3600 + electricity_exports = float(archetype['incomes']['electricity_export']['#text']) reduction_tax = float(archetype['incomes']['tax_reduction']['#text']) / 100 income = Income(construction_subsidy=construction, hvac_subsidy=hvac, @@ -75,7 +78,22 @@ class MontrealNewCatalog(Catalog): refurbishment_unit=_refurbishment_unit, reposition=None, reposition_unit=None, - lifetime=None) + lifetime=None, + maintenance=None, + maintenance_unit=None) + elif 'maintenance_cost' in item.keys(): + maintenance_cost = float(item['maintenance_cost']['#text']) + maintenance_unit = item['maintenance_cost']['@cost_unit'] + _item_description = ItemDescription(item_type, + initial_investment=None, + initial_investment_unit=None, + refurbishment=None, + refurbishment_unit=None, + reposition=None, + reposition_unit=None, + lifetime=None, + maintenance=maintenance_cost, + maintenance_unit=maintenance_unit) else: _reposition = float(item['reposition']['#text']) _reposition_unit = item['reposition']['@cost_unit'] @@ -89,7 +107,9 @@ class MontrealNewCatalog(Catalog): refurbishment_unit=None, reposition=_reposition, reposition_unit=_reposition_unit, - lifetime=_lifetime) + lifetime=_lifetime, + maintenance=None, + maintenance_unit=None) return _item_description @@ -137,13 +157,35 @@ class MontrealNewCatalog(Catalog): return capital_costs - @staticmethod - def _get_operational_costs(entry): + def load_fuel_rates(self): + rates = [] + with open(self._fuel_rates_path, 'r') as f: + fuel_rates = json.load(f) + for rate in fuel_rates['rates']['fuels']['rate']: + name = rate['name'] + rate_type = rate['rate_type'] + units = rate['units'] + values = rate['values'] + rates.append(PricingRate(name=name, rate_type=rate_type, units=units, values=values)) + return rates + + + def search_fuel_rates(self, rates, name): + variable = None + for rate in rates: + if rate.name == name: + variable = rate + return variable + + + + def _get_operational_costs(self, entry): fuels = [] + rates = self.load_fuel_rates() for item in entry['fuels']['fuel']: fuel_type = item['@fuel_type'] - fuel_variable = float(item['variable']['#text']) - fuel_variable_units = item['variable']['@cost_unit'] + fuel_variable = item['variable'] + variable = self.search_fuel_rates(rates, fuel_variable) fuel_fixed_monthly = None fuel_fixed_peak = None density = None @@ -165,20 +207,22 @@ class MontrealNewCatalog(Catalog): fuel = Fuel(fuel_type, fixed_monthly=fuel_fixed_monthly, fixed_power=fuel_fixed_peak, - variable=fuel_variable, - variable_units=fuel_variable_units, + variable=variable, 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']) - cooling_equipment_maintenance = float(entry['maintenance']['cooling_equipment']['#text']) + hvac_equipment = entry['maintenance']['hvac_equipment'] + items = [] + for item in hvac_equipment: + items.append(self.item_description(item, hvac_equipment[item])) + + photovoltaic_system_maintenance = float(entry['maintenance']['photovoltaic_system']['#text']) co2_emissions = float(entry['co2_cost']['#text']) _operational_cost = OperationalCost(fuels, - heating_equipment_maintenance, - cooling_equipment_maintenance, + items, photovoltaic_system_maintenance, co2_emissions) return _operational_cost diff --git a/hub/catalog_factories/data_models/cost/fuel.py b/hub/catalog_factories/data_models/cost/fuel.py index 5eb4866c..560b4da9 100644 --- a/hub/catalog_factories/data_models/cost/fuel.py +++ b/hub/catalog_factories/data_models/cost/fuel.py @@ -6,7 +6,7 @@ Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ from typing import Union - +from hub.catalog_factories.data_models.cost.pricing_rate import PricingRate class Fuel: """ @@ -16,7 +16,6 @@ class Fuel: fixed_monthly=None, fixed_power=None, variable=None, - variable_units=None, density=None, density_unit=None, lower_heating_value=None, @@ -26,7 +25,6 @@ class Fuel: 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 @@ -59,12 +57,12 @@ class Fuel: return None @property - def variable(self) -> Union[tuple[None, None], tuple[float, str]]: + def variable(self) -> Union[None, PricingRate]: """ Get variable costs in given units :return: None, None or float, str """ - return self._variable, self._variable_units + return self._variable @property def density(self) -> Union[tuple[None, None], tuple[float, str]]: @@ -84,11 +82,13 @@ class Fuel: def to_dictionary(self): """Class content to dictionary""" + variable_price = None + if self.variable is not None: + variable_price = self.variable.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], + 'variable operational costs': variable_price, 'density': self.density[0], 'density unit': self.density[1], 'lower heating value': self.lower_heating_value[0], diff --git a/hub/catalog_factories/data_models/cost/item_description.py b/hub/catalog_factories/data_models/cost/item_description.py index a193bb23..192d7503 100644 --- a/hub/catalog_factories/data_models/cost/item_description.py +++ b/hub/catalog_factories/data_models/cost/item_description.py @@ -19,7 +19,9 @@ class ItemDescription: refurbishment_unit=None, reposition=None, reposition_unit=None, - lifetime=None): + lifetime=None, + maintenance=None, + maintenance_unit=None): self._item_type = item_type self._initial_investment = initial_investment @@ -29,6 +31,8 @@ class ItemDescription: self._reposition = reposition self._reposition_unit = reposition_unit self._lifetime = lifetime + self._maintenance = maintenance + self._maintenance_unit = maintenance_unit @property def type(self): @@ -70,6 +74,14 @@ class ItemDescription: """ return self._lifetime + @property + def maintenance(self) -> Union[tuple[None, None], tuple[float, str]]: + """ + Get reposition costs of the specific item in given units + :return: None, None or float, str + """ + return self._maintenance, self._maintenance_unit + def to_dictionary(self): """Class content to dictionary""" content = {'Item': {'type': self.type, @@ -79,7 +91,9 @@ class ItemDescription: 'refurbishment units': self.refurbishment[1], 'reposition': self.reposition[0], 'reposition units': self.reposition[1], - 'life time [years]': self.lifetime + 'life time [years]': self.lifetime, + 'maintenance': self.maintenance[0], + 'maintenance units': self.maintenance[1] } } diff --git a/hub/catalog_factories/data_models/cost/operational_cost.py b/hub/catalog_factories/data_models/cost/operational_cost.py index 1275830a..4f69cceb 100644 --- a/hub/catalog_factories/data_models/cost/operational_cost.py +++ b/hub/catalog_factories/data_models/cost/operational_cost.py @@ -7,16 +7,15 @@ Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca from typing import List from hub.catalog_factories.data_models.cost.fuel import Fuel - +from hub.catalog_factories.data_models.cost.item_description import ItemDescription class OperationalCost: """ Operational cost class """ - def __init__(self, fuels, maintenance_heating, maintenance_cooling, maintenance_pv, co2): + def __init__(self, fuels, maintenance_hvac, maintenance_pv, co2): self._fuels = fuels - self._maintenance_heating = maintenance_heating - self._maintenance_cooling = maintenance_cooling + self._maintenance_hvac = maintenance_hvac self._maintenance_pv = maintenance_pv self._co2 = co2 @@ -30,20 +29,12 @@ class OperationalCost: return self._fuels @property - def maintenance_heating(self): + def maintenance_hvac(self) -> List[ItemDescription]: """ - Get cost of maintaining the heating system in currency/W + Get cost of maintaining the hvac system in currency/W :return: float """ - return self._maintenance_heating - - @property - def maintenance_cooling(self): - """ - Get cost of maintaining the cooling system in currency/W - :return: float - """ - return self._maintenance_cooling + return self._maintenance_hvac @property def maintenance_pv(self): @@ -64,11 +55,13 @@ class OperationalCost: def to_dictionary(self): """Class content to dictionary""" _fuels = [] + _hvac_maintenance = [] for _fuel in self.fuels: _fuels.append(_fuel.to_dictionary()) + for _hvac in self.maintenance_hvac: + _hvac_maintenance.append(_hvac.to_dictionary()) content = {'Maintenance': {'fuels': _fuels, - 'cost of maintaining the heating system [currency/W]': self.maintenance_heating, - 'cost of maintaining the cooling system [currency/W]': self.maintenance_cooling, + 'cost of maintaining the hvac system [currency/W]': _hvac_maintenance, 'cost of maintaining the PV system [currency/W]': self.maintenance_pv, 'cost of CO2 emissions [currency/kgCO2]': self.co2 } diff --git a/hub/catalog_factories/data_models/cost/pricing_rate.py b/hub/catalog_factories/data_models/cost/pricing_rate.py new file mode 100644 index 00000000..fe1c219c --- /dev/null +++ b/hub/catalog_factories/data_models/cost/pricing_rate.py @@ -0,0 +1,62 @@ +from typing import Union + + +class PricingRate: + def __init__(self, name=None, rate_type=None, time_range=None, units=None, values=None): + self._name = name + self._rate_type = rate_type + self._time_range = time_range + self._units = units + self._values = values + + + @property + def name(self): + """ + name of the rate + :return: str + """ + return self._name + + @property + def rate_type(self): + """ + type of rate between fixed and variable + :return: str + """ + return self._rate_type + + @property + def time_range(self) -> Union[None, str]: + """ + Get schedule time range from: + ['minute', 'hour', 'day', 'week', 'month', 'year'] + :return: None or str + """ + return self._time_range + + @property + def units(self): + """ + get the consumption unit + :return: str + """ + return self._units + + @property + def values(self): + """ + Get schedule values + :return: [Any] + """ + return self._values + + def to_dictionary(self): + """Class content to dictionary""" + content = {'Pricing': {'name': self.name, + 'time range': self.time_range, + 'type': self.rate_type, + 'units': self.units, + 'values': self.values} + } + return content diff --git a/hub/catalog_factories/data_models/energy_systems/emission_system.py b/hub/catalog_factories/data_models/energy_systems/emission_system.py index a8ac91b6..538954d3 100644 --- a/hub/catalog_factories/data_models/energy_systems/emission_system.py +++ b/hub/catalog_factories/data_models/energy_systems/emission_system.py @@ -10,7 +10,7 @@ class EmissionSystem: """ Emission system class """ - def __init__(self, system_id, model_name=None, system_type=None, parasitic_energy_consumption=None): + def __init__(self, system_id, model_name=None, system_type=None, parasitic_energy_consumption=0): self._system_id = system_id self._model_name = model_name diff --git a/hub/catalog_factories/energy_systems/montreal_custom_catalog.py b/hub/catalog_factories/energy_systems/montreal_custom_catalog.py index d3e37e36..cace9278 100644 --- a/hub/catalog_factories/energy_systems/montreal_custom_catalog.py +++ b/hub/catalog_factories/energy_systems/montreal_custom_catalog.py @@ -135,7 +135,7 @@ class MontrealCustomCatalog(Catalog): equipment_id = float(equipment['@id']) equipment_type = equipment['@type'] model_name = equipment['name'] - parasitic_consumption = None + parasitic_consumption = 0 if 'parasitic_consumption' in equipment: parasitic_consumption = float(equipment['parasitic_consumption']['#text']) / 100 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 d8910960..a4477ba2 100644 --- a/hub/catalog_factories/energy_systems/montreal_future_system_catalogue.py +++ b/hub/catalog_factories/energy_systems/montreal_future_system_catalogue.py @@ -262,7 +262,7 @@ class MontrealFutureSystemCatalogue(Catalog): system_id = None model_name = None system_type = None - parasitic_energy_consumption = None + parasitic_energy_consumption = 0 emission_system = EmissionSystem(system_id=system_id, model_name=model_name, system_type=system_type, @@ -298,7 +298,7 @@ class MontrealFutureSystemCatalogue(Catalog): layers = [insulation_layer, tank_layer] nominal_capacity = tes['nominal_capacity'] losses_ratio = tes['losses_ratio'] - heating_coil_capacity = None + heating_coil_capacity = tes['heating_coil_capacity'] storage_component = ThermalStorageSystem(storage_id=storage_id, model_name=model_name, type_energy_stored=type_energy_stored, @@ -338,7 +338,7 @@ class MontrealFutureSystemCatalogue(Catalog): nominal_capacity = template['nominal_capacity'] losses_ratio = template['losses_ratio'] volume = template['physical_characteristics']['volume'] - heating_coil_capacity = None + heating_coil_capacity = template['heating_coil_capacity'] storage_component = ThermalStorageSystem(storage_id=storage_id, model_name=model_name, type_energy_stored=type_energy_stored, diff --git a/hub/city_model_structure/building.py b/hub/city_model_structure/building.py index 09b5e2b1..132e7a5c 100644 --- a/hub/city_model_structure/building.py +++ b/hub/city_model_structure/building.py @@ -92,6 +92,7 @@ class Building(CityObject): logging.error('Building %s [%s] has an unexpected surface type %s.', self.name, self.aliases, surface.type) self._domestic_hot_water_peak_load = None self._fuel_consumption_breakdown = {} + self._pv_generation = {} @property def shell(self) -> Polyhedron: @@ -450,8 +451,8 @@ class Building(CityObject): monthly_values = PeakLoads(self).heating_peak_loads_from_methodology if monthly_values is None: return None - results[cte.MONTH] = [x * cte.WATTS_HOUR_TO_JULES for x in monthly_values] - results[cte.YEAR] = [max(monthly_values)] + results[cte.MONTH] = [x / cte.WATTS_HOUR_TO_JULES for x in monthly_values] + results[cte.YEAR] = [max(monthly_values) / cte.WATTS_HOUR_TO_JULES] return results @property @@ -467,8 +468,8 @@ class Building(CityObject): monthly_values = PeakLoads(self).cooling_peak_loads_from_methodology if monthly_values is None: return None - results[cte.MONTH] = [x * cte.WATTS_HOUR_TO_JULES for x in monthly_values] - results[cte.YEAR] = [max(monthly_values)] + results[cte.MONTH] = [x / cte.WATTS_HOUR_TO_JULES for x in monthly_values] + results[cte.YEAR] = [max(monthly_values) / cte.WATTS_HOUR_TO_JULES] return results @property @@ -483,8 +484,8 @@ class Building(CityObject): 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)] + results[cte.MONTH] = [x / cte.WATTS_HOUR_TO_JULES for x in monthly_values] + results[cte.YEAR] = [max(monthly_values) / cte.WATTS_HOUR_TO_JULES] return results @property @@ -809,39 +810,16 @@ class Building(CityObject): Get total electricity produced onsite in J return: dict """ - orientation_losses_factor = {cte.MONTH: {'north': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - 'east': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - 'south': [2.137931, 1.645503, 1.320946, 1.107817, 0.993213, 0.945175, - 0.967949, 1.065534, 1.24183, 1.486486, 1.918033, 2.210526], - 'west': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]}, - cte.YEAR: {'north': [0], - 'east': [0], - 'south': [1.212544], - 'west': [0]} - } - - # Add other systems whenever new ones appear - if self.energy_systems is None: - return self._onsite_electrical_production - for energy_system in self.energy_systems: - for generation_system in energy_system.generation_systems: - if generation_system.system_type == cte.PHOTOVOLTAIC: - if generation_system.electricity_efficiency is not None: - _efficiency = float(generation_system.electricity_efficiency) - else: - _efficiency = 0 - self._onsite_electrical_production = {} - for _key in self.roofs[0].global_irradiance.keys(): - _results = [0 for _ in range(0, len(self.roofs[0].global_irradiance[_key]))] - for surface in self.roofs: - if _key in orientation_losses_factor: - _results = [x + y * _efficiency * surface.perimeter_area - * surface.solar_collectors_area_reduction_factor * z - for x, y, z in zip(_results, surface.global_irradiance[_key], - orientation_losses_factor[_key]['south'])] - self._onsite_electrical_production[_key] = _results return self._onsite_electrical_production + @onsite_electrical_production.setter + def onsite_electrical_production(self, value): + """ + set onsite electrical production from external pv simulations + :return: + """ + self._onsite_electrical_production = value + @property def lower_corner(self): """ @@ -876,37 +854,39 @@ class Building(CityObject): if demand_type in generation_system.energy_consumption: fuel_breakdown[f'{generation_system.fuel_type}'][f'{demand_type}'] = ( generation_system.energy_consumption)[f'{demand_type}'][cte.YEAR][0] + storage_systems = generation_system.energy_storage_systems + if storage_systems: + for storage_system in storage_systems: + if storage_system.type_energy_stored == 'thermal' and storage_system.heating_coil_energy_consumption: + fuel_breakdown[cte.ELECTRICITY][f'{demand_type}'] += storage_system.heating_coil_energy_consumption[cte.YEAR][0] #TODO: When simulation models of all energy system archetypes are created, this part can be removed - heating = 0 - cooling = 0 - dhw = 0 + heating_fuels = [] + dhw_fuels = [] + for energy_system in self.energy_systems: + if cte.HEATING in energy_system.demand_types: + for generation_system in energy_system.generation_systems: + heating_fuels.append(generation_system.fuel_type) + if cte.DOMESTIC_HOT_WATER in energy_system.demand_types: + for generation_system in energy_system.generation_systems: + dhw_fuels.append(generation_system.fuel_type) for key in fuel_breakdown: - if cte.HEATING not in fuel_breakdown[key]: - heating += 1 if key == cte.ELECTRICITY and cte.COOLING not in fuel_breakdown[key]: - cooling += 1 - if cte.DOMESTIC_HOT_WATER not in fuel_breakdown[key]: - dhw += 1 - if heating > 0: - for energy_system in energy_systems: - if cte.HEATING in energy_system.demand_types: - for generation_system in energy_system.generation_systems: - fuel_breakdown[generation_system.fuel_type][cte.HEATING] = self.heating_consumption[cte.YEAR][0] / 3600 - if dhw > 0: - for energy_system in energy_systems: - if cte.DOMESTIC_HOT_WATER in energy_system.demand_types: - for generation_system in energy_system.generation_systems: - fuel_breakdown[generation_system.fuel_type][cte.DOMESTIC_HOT_WATER] = \ - self.domestic_hot_water_consumption[cte.YEAR][0] / 3600 - if cooling > 0: - for energy_system in energy_systems: - if cte.COOLING in energy_system.demand_types: - for generation_system in energy_system.generation_systems: - fuel_breakdown[generation_system.fuel_type][cte.COOLING] = self.cooling_consumption[cte.YEAR][0] / 3600 - - - - - + for energy_system in energy_systems: + if cte.COOLING in energy_system.demand_types and cte.COOLING not in fuel_breakdown[key]: + for generation_system in energy_system.generation_systems: + fuel_breakdown[generation_system.fuel_type][cte.COOLING] = self.cooling_consumption[cte.YEAR][0] + for fuel in heating_fuels: + if cte.HEATING not in fuel_breakdown[fuel]: + for energy_system in energy_systems: + if cte.HEATING in energy_system.demand_types: + for generation_system in energy_system.generation_systems: + fuel_breakdown[generation_system.fuel_type][cte.HEATING] = self.heating_consumption[cte.YEAR][0] + for fuel in dhw_fuels: + if cte.DOMESTIC_HOT_WATER not in fuel_breakdown[fuel]: + for energy_system in energy_systems: + if cte.DOMESTIC_HOT_WATER in energy_system.demand_types: + for generation_system in energy_system.generation_systems: + 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/building_demand/surface.py b/hub/city_model_structure/building_demand/surface.py index 2cd42755..65f90ae3 100644 --- a/hub/city_model_structure/building_demand/surface.py +++ b/hub/city_model_structure/building_demand/surface.py @@ -46,6 +46,8 @@ class Surface: self._vegetation = None self._percentage_shared = None self._solar_collectors_area_reduction_factor = None + self._global_irradiance_tilted = {} + self._installed_solar_collector_area = None @property def name(self): @@ -384,3 +386,35 @@ class Surface: :param value: float """ self._solar_collectors_area_reduction_factor = value + + @property + def global_irradiance_tilted(self) -> dict: + """ + Get global irradiance on a tilted surface in J/m2 + :return: dict + """ + return self._global_irradiance_tilted + + @global_irradiance_tilted.setter + def global_irradiance_tilted(self, value): + """ + Set global irradiance on a tilted surface in J/m2 + :param value: dict + """ + self._global_irradiance_tilted = value + + @property + def installed_solar_collector_area(self): + """ + Get installed solar collector area in m2 + :return: dict + """ + return self._installed_solar_collector_area + + @installed_solar_collector_area.setter + def installed_solar_collector_area(self, value): + """ + Set installed solar collector area in m2 + :return: dict + """ + self._installed_solar_collector_area = value \ No newline at end of file diff --git a/hub/city_model_structure/city_object.py b/hub/city_model_structure/city_object.py index c18a4aa0..0d281b65 100644 --- a/hub/city_model_structure/city_object.py +++ b/hub/city_model_structure/city_object.py @@ -41,9 +41,10 @@ class CityObject: self._ground_temperature = {} self._global_horizontal = {} self._diffuse = {} - self._beam = {} + self._direct_normal = {} self._sensors = [] self._neighbours = None + self._beam = {} @property def level_of_detail(self) -> LevelOfDetail: @@ -238,20 +239,20 @@ class CityObject: self._diffuse = value @property - def beam(self) -> dict: + def direct_normal(self) -> dict: """ Get beam radiation surrounding the city object in J/m2 :return: dict{dict{[float]}} """ - return self._beam + return self._direct_normal - @beam.setter - def beam(self, value): + @direct_normal.setter + def direct_normal(self, value): """ Set beam radiation surrounding the city object in J/m2 :param value: dict{dict{[float]}} """ - self._beam = value + self._direct_normal = value @property def lower_corner(self): @@ -302,3 +303,19 @@ class CityObject: Set the list of neighbour_objects and their properties associated to the current city_object """ self._neighbours = value + + @property + def beam(self) -> dict: + """ + Get beam radiation surrounding the city object in J/m2 + :return: dict{dict{[float]}} + """ + return self._beam + + @beam.setter + def beam(self, value): + """ + Set beam radiation surrounding the city object in J/m2 + :param value: dict{dict{[float]}} + """ + self._beam = value diff --git a/hub/city_model_structure/energy_systems/emission_system.py b/hub/city_model_structure/energy_systems/emission_system.py index 32bf7c17..e8773013 100644 --- a/hub/city_model_structure/energy_systems/emission_system.py +++ b/hub/city_model_structure/energy_systems/emission_system.py @@ -13,7 +13,7 @@ class EmissionSystem: def __init__(self): self._model_name = None self._type = None - self._parasitic_energy_consumption = None + self._parasitic_energy_consumption = 0 @property def model_name(self): diff --git a/hub/city_model_structure/energy_systems/pv_generation_system.py b/hub/city_model_structure/energy_systems/pv_generation_system.py index 13409c7e..ddbc847a 100644 --- a/hub/city_model_structure/energy_systems/pv_generation_system.py +++ b/hub/city_model_structure/energy_systems/pv_generation_system.py @@ -26,6 +26,10 @@ class PvGenerationSystem(GenerationSystem): self._width = None self._height = None self._electricity_power = None + self._tilt_angle = None + self._surface_azimuth = None + self._solar_altitude_angle = None + self._solar_azimuth_angle = None @property def nominal_electricity_output(self): @@ -202,3 +206,35 @@ class PvGenerationSystem(GenerationSystem): :param value: float """ self._electricity_power = value + + @property + def tilt_angle(self): + """ + Get tilt angle of PV system in degrees + :return: float + """ + return self._tilt_angle + + @tilt_angle.setter + def tilt_angle(self, value): + """ + Set PV system tilt angle in degrees + :param value: float + """ + self._tilt_angle = value + + @property + def surface_azimuth(self): + """ + Get surface azimuth angle of PV system in degrees. 0 is North + :return: float + """ + return self._surface_azimuth + + @surface_azimuth.setter + def surface_azimuth(self, value): + """ + Set PV system tilt angle in degrees + :param value: float + """ + self._surface_azimuth = 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 01a910f7..fd9b8f81 100644 --- a/hub/city_model_structure/energy_systems/thermal_storage_system.py +++ b/hub/city_model_structure/energy_systems/thermal_storage_system.py @@ -24,6 +24,7 @@ class ThermalStorageSystem(EnergyStorageSystem): self._maximum_operating_temperature = None self._heating_coil_capacity = None self._temperature = None + self._heating_coil_energy_consumption = None @property def volume(self): @@ -95,7 +96,7 @@ class ThermalStorageSystem(EnergyStorageSystem): Get heating coil capacity in Watts :return: float """ - return self._maximum_operating_temperature + return self._heating_coil_capacity @heating_coil_capacity.setter def heating_coil_capacity(self, value): @@ -120,3 +121,19 @@ class ThermalStorageSystem(EnergyStorageSystem): :param value: dict{[float]} """ self._temperature = value + + @property + def heating_coil_energy_consumption(self) -> dict: + """ + Get fuel consumption in W, m3, or kg + :return: dict{[float]} + """ + return self._heating_coil_energy_consumption + + @heating_coil_energy_consumption.setter + def heating_coil_energy_consumption(self, value): + """ + Set fuel consumption in W, m3, or kg + :param value: dict{[float]} + """ + self._heating_coil_energy_consumption = value diff --git a/hub/data/costs/fuel_rates.json b/hub/data/costs/fuel_rates.json new file mode 100644 index 00000000..0841e818 --- /dev/null +++ b/hub/data/costs/fuel_rates.json @@ -0,0 +1,106 @@ +{ + "rates": { + "fuels": { + "rate": [ + { + "name": "Electricity-D", + "fuel_type": "Electricity", + "rate_name": "D", + "units": "CAD/kWh", + "usage_type": "residential", + "maximum_power_demand_kW": 65, + "rate_type": "fixed", + "notes": null, + "start_date": null, + "end_date": null, + "values": [ + 0.075 + ] + }, + { + "name": "Electricity-Flex-D", + "fuel_type": "Electricity", + "rate_name": "Flex-D", + "units": "CAD/kWh", + "usage_type": "residential", + "maximum_power_demand_kW": 65, + "rate_type": "variable", + "notes": null, + "start_date": null, + "end_date": null, + "values": [ + 0.075, + 0.075, + 0.075, + 0.075, + 0.075, + 0.075, + 0.551, + 0.551, + 0.551, + 0.075, + 0.075, + 0.075, + 0.075, + 0.075, + 0.075, + 0.075, + 0.551, + 0.551, + 0.551, + 0.551, + 0.075, + 0.075, + 0.075, + 0.075 + ] + }, + { + "name": "Gas-Energir", + "fuel_type": "Gas", + "rate_name": null, + "units": "CAD/m3", + "usage_type": "residential", + "maximum_power_demand_kW": null, + "rate_type": "fixed", + "notes": null, + "start_date": null, + "end_date": null, + "values": [ + 0.4 + ] + }, + { + "name": "Diesel-Fixed", + "fuel_type": "Diesel", + "rate_name": null, + "units": "CAD/l", + "usage_type": "residential", + "maximum_power_demand_kW": null, + "rate_type": "fixed", + "notes": null, + "start_date": null, + "end_date": null, + "values": [ + 1.2 + ] + }, + { + "name": "Biomass-Fixed", + "fuel_type": "Biomass", + "rate_name": null, + "units": "CAD/kg", + "usage_type": "residential", + "maximum_power_demand_kW": null, + "rate_type": "fixed", + "notes": null, + "start_date": null, + "end_date": null, + "values": [ + 0.04 + ] + } + ] + } + } +} \ No newline at end of file diff --git a/hub/data/costs/montreal_costs_completed.xml b/hub/data/costs/montreal_costs_completed.xml index 81b67768..6b3fc41f 100644 --- a/hub/data/costs/montreal_costs_completed.xml +++ b/hub/data/costs/montreal_costs_completed.xml @@ -25,8 +25,8 @@ - 0 - 0 + 300 + 300 25 @@ -124,34 +124,58 @@ - 12.27 - 0 - 0.075 - - + 12.27 + 0 + Electricity-D + + + 12.27 + 0 + Electricity-Flex-D 17.71 - 0.4 + Gas-Energir 0.777 47.1 - 1.2 + Diesel-Fixed 0.846 42.6 - 0.04 + Biomass-Fixed 18 - 40 - 40 + + + 100 + + + 60 + + + 50 + + + 50 + + + 100 + + + 60 + + + 50 + + 1 30 @@ -163,7 +187,7 @@ 1.5 3.6 - 0.07 + 0.075 5 @@ -294,31 +318,57 @@ 12.27 0 - 0.075 + Electricity-D + + + 12.27 + 0 + Electricity-Flex-D 17.71 - 0.0640 + Gas-Energir 0.777 47.1 - 1.2 + Diesel-Fixed 0.846 42.6 - 0.04 + Biomass-Fixed 18 - 40 - 40 - 0 + + + 100 + + + 60 + + + 50 + + + 50 + + + 100 + + + 60 + + + 50 + + + 1 30 diff --git a/hub/data/energy_systems/montreal_future_systems.xml b/hub/data/energy_systems/montreal_future_systems.xml index b42993d4..b51c9488 100644 --- a/hub/data/energy_systems/montreal_future_systems.xml +++ b/hub/data/energy_systems/montreal_future_systems.xml @@ -911,7 +911,7 @@ - + 4.5 @@ -931,7 +931,13 @@ - + + bi-quadratic + COP + source_temperature + supply_temperature + + True @@ -1049,7 +1055,7 @@ 3.5 electricity - Water + Air Water @@ -1065,7 +1071,13 @@ - + + bi-quadratic + COP + source_temperature + supply_temperature + + @@ -1259,7 +1271,7 @@ sensible - + 5000 @@ -1426,6 +1438,29 @@ 27 + + 11 + Central Heating System َASHP Gas-Boiler TES + schemas/ASHP+TES+GasBoiler.jpg + + heating + + + 23 + 16 + + + + 12 + Unitary ASHP Cooling System + schemas/ASHP+TES+GasBoiler.jpg + + cooling + + + 23 + + @@ -1516,6 +1551,23 @@ 10 + + Central Heating+Unitary Cooling+Unitary DHW + + 10 + 11 + 12 + + + + Central Heating+Unitary Cooling+Unitary DHW+PV + + 7 + 10 + 11 + 12 + + diff --git a/hub/data/energy_systems/schemas/PV+4Pipe+DHW.jpg b/hub/data/energy_systems/schemas/PV+4Pipe+DHW.jpg new file mode 100644 index 00000000..7daad987 Binary files /dev/null and b/hub/data/energy_systems/schemas/PV+4Pipe+DHW.jpg differ diff --git a/hub/exports/formats/simplified_radiosity_algorithm.py b/hub/exports/formats/simplified_radiosity_algorithm.py index f9ea7f1d..25189419 100644 --- a/hub/exports/formats/simplified_radiosity_algorithm.py +++ b/hub/exports/formats/simplified_radiosity_algorithm.py @@ -67,7 +67,7 @@ class SimplifiedRadiosityAlgorithm: i = (total_days + day - 1) * 24 + hour - 1 representative_building = self._city.buildings[0] _global = representative_building.diffuse[cte.HOUR][i] / cte.WATTS_HOUR_TO_JULES - _beam = representative_building.beam[cte.HOUR][i] / cte.WATTS_HOUR_TO_JULES + _beam = representative_building.direct_normal[cte.HOUR][i] / cte.WATTS_HOUR_TO_JULES content += f'{day} {month} {hour} {_global} {_beam}\n' with open(file, 'w', encoding='utf-8') as file: file.write(content) diff --git a/hub/helpers/constants.py b/hub/helpers/constants.py index af22e94d..ad32c835 100644 --- a/hub/helpers/constants.py +++ b/hub/helpers/constants.py @@ -10,6 +10,7 @@ Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca KELVIN = 273.15 WATER_DENSITY = 1000 # kg/m3 WATER_HEAT_CAPACITY = 4182 # J/kgK +WATER_THERMAL_CONDUCTIVITY = 0.65 # W/mK NATURAL_GAS_LHV = 36.6e6 # J/m3 AIR_DENSITY = 1.293 # kg/m3 AIR_HEAT_CAPACITY = 1005.2 # J/kgK diff --git a/hub/imports/construction/nrcan_physics_parameters.py b/hub/imports/construction/nrcan_physics_parameters.py index e57aa22e..e39774e7 100644 --- a/hub/imports/construction/nrcan_physics_parameters.py +++ b/hub/imports/construction/nrcan_physics_parameters.py @@ -32,10 +32,21 @@ class NrcanPhysicsParameters: city = self._city nrcan_catalog = ConstructionCatalogFactory('nrcan').catalog for building in city.buildings: - if building.function not in Dictionaries().hub_function_to_nrcan_construction_function: - logging.error('Building %s has an unknown building function %s', building.name, building.function) + main_function = None + functions = building.function.split('_') + if len(functions) > 1: + maximum_percentage = 0 + for function in functions: + percentage_and_function = function.split('-') + if float(percentage_and_function[0]) > maximum_percentage: + maximum_percentage = float(percentage_and_function[0]) + main_function = percentage_and_function[-1] + else: + main_function = functions[-1] + if main_function not in Dictionaries().hub_function_to_nrcan_construction_function: + logging.error('Building %s has an unknown building function %s', building.name, main_function) continue - function = Dictionaries().hub_function_to_nrcan_construction_function[building.function] + function = Dictionaries().hub_function_to_nrcan_construction_function[main_function] try: archetype = self._search_archetype(nrcan_catalog, function, building.year_of_construction, self._climate_zone) 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 15833c9b..eb1b9e92 100644 --- a/hub/imports/energy_systems/montreal_custom_energy_system_parameters.py +++ b/hub/imports/energy_systems/montreal_custom_energy_system_parameters.py @@ -136,10 +136,14 @@ class MontrealCustomEnergySystemParameters: _distribution_system.distribution_consumption_variable_flow = \ archetype_distribution_system.distribution_consumption_variable_flow _distribution_system.heat_losses = archetype_distribution_system.heat_losses - _emission_system = None + _generic_emission_system = None if archetype_distribution_system.emission_systems is not None: - _emission_system = EmissionSystem() - _distribution_system.emission_systems = [_emission_system] + _emission_systems = [] + for emission_system in archetype_distribution_system.emission_systems: + _generic_emission_system = EmissionSystem() + _generic_emission_system.parasitic_energy_consumption = emission_system.parasitic_energy_consumption + _emission_systems.append(_generic_emission_system) + _distribution_system.emission_systems = _emission_systems _distribution_systems.append(_distribution_system) return _distribution_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 8612f845..b5628d9f 100644 --- a/hub/imports/energy_systems/montreal_future_energy_systems_parameters.py +++ b/hub/imports/energy_systems/montreal_future_energy_systems_parameters.py @@ -82,8 +82,7 @@ class MontrealFutureEnergySystemParameters: return _generic_energy_systems - @staticmethod - def _create_generation_systems(archetype_system): + def _create_generation_systems(self, archetype_system): _generation_systems = [] archetype_generation_systems = archetype_system.generation_systems if archetype_generation_systems is not None: @@ -107,6 +106,7 @@ class MontrealFutureEnergySystemParameters: _generation_system.cell_temperature_coefficient = archetype_generation_system.cell_temperature_coefficient _generation_system.width = archetype_generation_system.width _generation_system.height = archetype_generation_system.height + _generation_system.tilt_angle = self._city.latitude _generic_storage_system = None if archetype_generation_system.energy_storage_systems is not None: _generic_storage_system = ElectricalStorageSystem() @@ -160,6 +160,7 @@ class MontrealFutureEnergySystemParameters: _generic_storage_system.height = storage_system.height _generic_storage_system.layers = storage_system.layers _generic_storage_system.storage_medium = storage_system.storage_medium + _generic_storage_system.heating_coil_capacity = storage_system.heating_coil_capacity _storage_systems.append(_generic_storage_system) _generation_system.energy_storage_systems = _storage_systems if archetype_generation_system.domestic_hot_water: @@ -184,10 +185,14 @@ class MontrealFutureEnergySystemParameters: _distribution_system.distribution_consumption_variable_flow = \ archetype_distribution_system.distribution_consumption_variable_flow _distribution_system.heat_losses = archetype_distribution_system.heat_losses - _emission_system = None + _generic_emission_system = None if archetype_distribution_system.emission_systems is not None: - _emission_system = EmissionSystem() - _distribution_system.emission_systems = [_emission_system] + _emission_systems = [] + for emission_system in archetype_distribution_system.emission_systems: + _generic_emission_system = EmissionSystem() + _generic_emission_system.parasitic_energy_consumption = emission_system.parasitic_energy_consumption + _emission_systems.append(_generic_emission_system) + _distribution_system.emission_systems = _emission_systems _distribution_systems.append(_distribution_system) return _distribution_systems diff --git a/hub/imports/geometry/geojson.py b/hub/imports/geometry/geojson.py index b07a6a7b..3505c27a 100644 --- a/hub/imports/geometry/geojson.py +++ b/hub/imports/geometry/geojson.py @@ -127,6 +127,19 @@ class Geojson: function = None if self._function_field is not None: function = str(feature['properties'][self._function_field]) + if function == 'Mixed use' or function == 'mixed use': + function_parts = [] + for key, value in feature['properties'].items(): + if key.startswith("mixed_type_") and not key.endswith("_percentage"): + type_key = key + percentage_key = f"{key}_percentage" + if percentage_key in feature['properties']: + if self._function_to_hub is not None and feature['properties'][type_key] in self._function_to_hub: + usage_function = self._function_to_hub[feature['properties'][type_key]] + function_parts.append(f"{feature['properties'][percentage_key]}-{usage_function}") + else: + function_parts.append(f"{feature['properties'][percentage_key]}-{feature['properties'][type_key]}") + function = "_".join(function_parts) if self._function_to_hub is not None: # use the transformation dictionary to retrieve the proper function if function in self._function_to_hub: diff --git a/hub/imports/results/ep_multiple_buildings.py b/hub/imports/results/ep_multiple_buildings.py index 9da12c7a..bd9a2dd3 100644 --- a/hub/imports/results/ep_multiple_buildings.py +++ b/hub/imports/results/ep_multiple_buildings.py @@ -60,9 +60,12 @@ class EnergyPlusMultipleBuildings: for building in self._city.buildings: building.heating_demand[cte.HOUR] = building_energy_demands[f'Building {building.name} Heating Demand (J)'] building.cooling_demand[cte.HOUR] = building_energy_demands[f'Building {building.name} Cooling Demand (J)'] - building.domestic_hot_water_heat_demand[cte.HOUR] = building_energy_demands[f'Building {building.name} DHW Demand (W)'] - building.appliances_electrical_demand[cte.HOUR] = building_energy_demands[f'Building {building.name} Appliances (W)'] - building.lighting_electrical_demand[cte.HOUR] = building_energy_demands[f'Building {building.name} Lighting (W)'] + building.domestic_hot_water_heat_demand[cte.HOUR] = \ + [x * cte.WATTS_HOUR_TO_JULES for x in building_energy_demands[f'Building {building.name} DHW Demand (W)']] + building.appliances_electrical_demand[cte.HOUR] = \ + [x * cte.WATTS_HOUR_TO_JULES for x in building_energy_demands[f'Building {building.name} Appliances (W)']] + building.lighting_electrical_demand[cte.HOUR] = \ + [x * cte.WATTS_HOUR_TO_JULES for x in building_energy_demands[f'Building {building.name} Lighting (W)']] building.heating_demand[cte.MONTH] = MonthlyValues.get_total_month(building.heating_demand[cte.HOUR]) building.cooling_demand[cte.MONTH] = MonthlyValues.get_total_month(building.cooling_demand[cte.HOUR]) building.domestic_hot_water_heat_demand[cte.MONTH] = ( diff --git a/hub/imports/results/simplified_radiosity_algorithm.py b/hub/imports/results/simplified_radiosity_algorithm.py index 9b09b5f8..3824e7ce 100644 --- a/hub/imports/results/simplified_radiosity_algorithm.py +++ b/hub/imports/results/simplified_radiosity_algorithm.py @@ -34,7 +34,7 @@ class SimplifiedRadiosityAlgorithm: for key in self._results: _irradiance = {} header_name = key.split(':') - result = [x for x in self._results[key]] + result = [x * cte.WATTS_HOUR_TO_JULES for x in self._results[key]] city_object_name = header_name[1] building = self._city.city_object(city_object_name) surface_id = header_name[2] diff --git a/hub/imports/usage/comnet_usage_parameters.py b/hub/imports/usage/comnet_usage_parameters.py index 02f9b77e..08d8175c 100644 --- a/hub/imports/usage/comnet_usage_parameters.py +++ b/hub/imports/usage/comnet_usage_parameters.py @@ -35,29 +35,60 @@ class ComnetUsageParameters: city = self._city comnet_catalog = UsageCatalogFactory('comnet').catalog for building in city.buildings: - usage_name = Dictionaries().hub_usage_to_comnet_usage[building.function] - try: - archetype_usage = self._search_archetypes(comnet_catalog, usage_name) - except KeyError: - logging.error('Building %s has unknown usage archetype for usage %s', building.name, usage_name) - continue - - for internal_zone in building.internal_zones: - if internal_zone.area is None: - raise TypeError('Internal zone area not defined, ACH cannot be calculated') - if internal_zone.volume is None: - raise TypeError('Internal zone volume not defined, ACH cannot be calculated') - if internal_zone.area <= 0: - raise TypeError('Internal zone area is zero, ACH cannot be calculated') - volume_per_area = internal_zone.volume / internal_zone.area - usage = Usage() - usage.name = usage_name - self._assign_values(usage, archetype_usage, volume_per_area, building.cold_water_temperature) - usage.percentage = 1 - self._calculate_reduced_values_from_extended_library(usage, archetype_usage) - - internal_zone.usages = [usage] + usages = [] + comnet_archetype_usages = [] + building_functions = building.function.split('_') + for function in building_functions: + usages.append(function.split('-')) + for usage in usages: + comnet_usage_name = Dictionaries().hub_usage_to_comnet_usage[usage[-1]] + try: + comnet_archetype_usage = self._search_archetypes(comnet_catalog, comnet_usage_name) + comnet_archetype_usages.append(comnet_archetype_usage) + except KeyError: + logging.error('Building %s has unknown usage archetype for usage %s', building.name, comnet_usage_name) + continue + for (i, internal_zone) in enumerate(building.internal_zones): + internal_zone_usages = [] + if len(building.internal_zones) > 1: + volume_per_area = 0 + if internal_zone.area is None: + logging.error('Building %s has internal zone area not defined, ACH cannot be calculated for usage %s', + building.name, usages[i][-1]) + continue + if internal_zone.volume is None: + logging.error('Building %s has internal zone volume not defined, ACH cannot be calculated for usage %s', + building.name, usages[i][-1]) + continue + if internal_zone.area <= 0: + logging.error('Building %s has internal zone area equal to 0, ACH cannot be calculated for usage %s', + building.name, usages[i][-1]) + continue + volume_per_area += internal_zone.volume / internal_zone.area + usage = Usage() + usage.name = usages[i][-1] + self._assign_values(usage, comnet_archetype_usages[i], volume_per_area, building.cold_water_temperature) + usage.percentage = 1 + self._calculate_reduced_values_from_extended_library(usage, comnet_archetype_usages[i]) + internal_zone_usages.append(usage) + else: + if building.storeys_above_ground is None: + logging.error('Building %s no number of storeys assigned, ACH cannot be calculated for usage %s', + building.name, usages) + continue + volume_per_area = building.volume / building.floor_area / building.storeys_above_ground + for (j, mixed_usage) in enumerate(usages): + usage = Usage() + usage.name = mixed_usage[-1] + if len(usages) > 1: + usage.percentage = float(mixed_usage[0]) / 100 + else: + usage.percentage = 1 + self._assign_values(usage, comnet_archetype_usages[j], volume_per_area, building.cold_water_temperature) + self._calculate_reduced_values_from_extended_library(usage, comnet_archetype_usages[j]) + internal_zone_usages.append(usage) + internal_zone.usages = internal_zone_usages @staticmethod def _search_archetypes(comnet_catalog, usage_name): comnet_archetypes = comnet_catalog.entries('archetypes').usages diff --git a/hub/imports/usage/nrcan_usage_parameters.py b/hub/imports/usage/nrcan_usage_parameters.py index 9d718d33..4ff99a29 100644 --- a/hub/imports/usage/nrcan_usage_parameters.py +++ b/hub/imports/usage/nrcan_usage_parameters.py @@ -33,53 +33,72 @@ class NrcanUsageParameters: city = self._city nrcan_catalog = UsageCatalogFactory('nrcan').catalog comnet_catalog = UsageCatalogFactory('comnet').catalog - for building in city.buildings: - usage_name = Dictionaries().hub_usage_to_nrcan_usage[building.function] - try: - archetype_usage = self._search_archetypes(nrcan_catalog, usage_name) - except KeyError: - logging.error('Building %s has unknown usage archetype for usage %s', building.name, usage_name) - continue + usages = [] + nrcan_archetype_usages = [] + comnet_archetype_usages = [] + building_functions = building.function.split('_') + for function in building_functions: + usages.append(function.split('-')) + for usage in usages: + usage_name = Dictionaries().hub_usage_to_nrcan_usage[usage[-1]] + try: + archetype_usage = self._search_archetypes(nrcan_catalog, usage_name) + nrcan_archetype_usages.append(archetype_usage) + except KeyError: + logging.error('Building %s has unknown usage archetype for usage %s', building.name, usage_name) + continue + comnet_usage_name = Dictionaries().hub_usage_to_comnet_usage[usage[-1]] + try: + comnet_archetype_usage = self._search_archetypes(comnet_catalog, comnet_usage_name) + comnet_archetype_usages.append(comnet_archetype_usage) + except KeyError: + logging.error('Building %s has unknown usage archetype for usage %s', building.name, comnet_usage_name) + continue - comnet_usage_name = Dictionaries().hub_usage_to_comnet_usage[building.function] - try: - comnet_archetype_usage = self._search_archetypes(comnet_catalog, comnet_usage_name) - except KeyError: - logging.error('Building %s has unknown usage archetype for usage %s', building.name, comnet_usage_name) - continue - - for internal_zone in building.internal_zones: + for (i, internal_zone) in enumerate(building.internal_zones): + internal_zone_usages = [] if len(building.internal_zones) > 1: volume_per_area = 0 if internal_zone.area is None: logging.error('Building %s has internal zone area not defined, ACH cannot be calculated for usage %s', - building.name, usage_name) + building.name, usages[i][-1]) continue if internal_zone.volume is None: logging.error('Building %s has internal zone volume not defined, ACH cannot be calculated for usage %s', - building.name, usage_name) + building.name, usages[i][-1]) continue if internal_zone.area <= 0: logging.error('Building %s has internal zone area equal to 0, ACH cannot be calculated for usage %s', - building.name, usage_name) + building.name, usages[i][-1]) continue volume_per_area += internal_zone.volume / internal_zone.area + usage = Usage() + usage.name = usages[i][-1] + self._assign_values(usage, nrcan_archetype_usages[i], volume_per_area, building.cold_water_temperature) + self._assign_comnet_extra_values(usage, comnet_archetype_usages[i], nrcan_archetype_usages[i].occupancy.occupancy_density) + usage.percentage = 1 + self._calculate_reduced_values_from_extended_library(usage, nrcan_archetype_usages[i]) + internal_zone_usages.append(usage) else: if building.storeys_above_ground is None: logging.error('Building %s no number of storeys assigned, ACH cannot be calculated for usage %s', - building.name, usage_name) + building.name, usages) continue volume_per_area = building.volume / building.floor_area / building.storeys_above_ground + for (j, mixed_usage) in enumerate(usages): + usage = Usage() + usage.name = mixed_usage[-1] + if len(usages) > 1: + usage.percentage = float(mixed_usage[0]) / 100 + else: + usage.percentage = 1 + self._assign_values(usage, nrcan_archetype_usages[j], volume_per_area, building.cold_water_temperature) + self._assign_comnet_extra_values(usage, comnet_archetype_usages[j], nrcan_archetype_usages[j].occupancy.occupancy_density) + self._calculate_reduced_values_from_extended_library(usage, nrcan_archetype_usages[j]) + internal_zone_usages.append(usage) - usage = Usage() - usage.name = usage_name - self._assign_values(usage, archetype_usage, volume_per_area, building.cold_water_temperature) - self._assign_comnet_extra_values(usage, comnet_archetype_usage, archetype_usage.occupancy.occupancy_density) - usage.percentage = 1 - self._calculate_reduced_values_from_extended_library(usage, archetype_usage) - - internal_zone.usages = [usage] + internal_zone.usages = internal_zone_usages @staticmethod def _search_archetypes(catalog, usage_name): diff --git a/hub/imports/weather/epw_weather_parameters.py b/hub/imports/weather/epw_weather_parameters.py index c2ed2e8f..0c362261 100644 --- a/hub/imports/weather/epw_weather_parameters.py +++ b/hub/imports/weather/epw_weather_parameters.py @@ -114,18 +114,22 @@ class EpwWeatherParameters: for x in self._weather_values['global_horizontal_radiation_wh_m2']] building.diffuse[cte.HOUR] = [x * cte.WATTS_HOUR_TO_JULES for x in self._weather_values['diffuse_horizontal_radiation_wh_m2']] - building.beam[cte.HOUR] = [x * cte.WATTS_HOUR_TO_JULES - for x in self._weather_values['direct_normal_radiation_wh_m2']] + building.direct_normal[cte.HOUR] = [x * cte.WATTS_HOUR_TO_JULES + for x in self._weather_values['direct_normal_radiation_wh_m2']] + building.beam[cte.HOUR] = [building.global_horizontal[cte.HOUR][i] - + building.diffuse[cte.HOUR][i] + for i in range(len(building.global_horizontal[cte.HOUR]))] building.cold_water_temperature[cte.HOUR] = wh().cold_water_temperature(building.external_temperature[cte.HOUR]) + # create the monthly and yearly values out of the hourly for building in self._city.buildings: building.external_temperature[cte.MONTH] = \ MonthlyValues().get_mean_values(building.external_temperature[cte.HOUR]) - building.external_temperature[cte.YEAR] = [sum(building.external_temperature[cte.HOUR]) / 9870] + building.external_temperature[cte.YEAR] = [sum(building.external_temperature[cte.HOUR]) / 8760] building.cold_water_temperature[cte.MONTH] = \ MonthlyValues().get_mean_values(building.cold_water_temperature[cte.HOUR]) - building.cold_water_temperature[cte.YEAR] = [sum(building.cold_water_temperature[cte.HOUR]) / 9870] + building.cold_water_temperature[cte.YEAR] = [sum(building.cold_water_temperature[cte.HOUR]) / 8760] # If the usage has already being imported, the domestic hot water missing values must be calculated here that # the cold water temperature is finally known diff --git a/hub/imports/weather/helpers/weather.py b/hub/imports/weather/helpers/weather.py index 7603cb5b..755f9ad3 100644 --- a/hub/imports/weather/helpers/weather.py +++ b/hub/imports/weather/helpers/weather.py @@ -8,7 +8,7 @@ Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca import logging import math import hub.helpers.constants as cte - +from datetime import datetime, timedelta class Weather: """ @@ -55,25 +55,19 @@ class Weather: # and Craig Christensen, National Renewable Energy Laboratory # ambient temperatures( in °C) # cold water temperatures( in °C) - ambient_temperature_fahrenheit = [] - average_temperature = 0 - maximum_temperature = -1000 - minimum_temperature = 1000 - for temperature in ambient_temperature: - value = temperature * 9 / 5 + 32 - ambient_temperature_fahrenheit.append(value) - average_temperature += value / 8760 - if value > maximum_temperature: - maximum_temperature = value - if value < minimum_temperature: - minimum_temperature = value - delta_temperature = maximum_temperature - minimum_temperature - ratio = 0.4 + 0.01 * (average_temperature - 44) - lag = 35 - 1 * (average_temperature - 44) + t_out_fahrenheit = [1.8 * t_out + 32 for t_out in ambient_temperature] + t_out_average = sum(t_out_fahrenheit) / len(t_out_fahrenheit) + max_difference = max(t_out_fahrenheit) - min(t_out_fahrenheit) + ratio = 0.4 + 0.01 * (t_out_average - 44) + lag = 35 - (t_out_average - 35) + number_of_day = [a for a in range(1, 366)] + day_of_year = [day for day in number_of_day for _ in range(24)] + cold_temperature_fahrenheit = [] cold_temperature = [] - for temperature in ambient_temperature_fahrenheit: - radians = (0.986 * (temperature-15-lag) - 90) * math.pi / 180 - cold_temperature.append((average_temperature + 6 + ratio * (delta_temperature/2) * math.sin(radians) - 32) * 5/9) + for i in range(len(ambient_temperature)): + cold_temperature_fahrenheit.append(t_out_average + 6 + ratio * (max_difference / 2) * + math.sin(math.radians(0.986 * (day_of_year[i] - 15 - lag) - 90))) + cold_temperature.append((cold_temperature_fahrenheit[i] - 32) / 1.8) return cold_temperature def epw_file(self, region_code): diff --git a/input_files/output_buildings_expanded.geojson b/input_files/output_buildings_expanded.geojson new file mode 100644 index 00000000..43fd4d3f --- /dev/null +++ b/input_files/output_buildings_expanded.geojson @@ -0,0 +1,863 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -73.56769087843276, + 45.49251875903776 + ], + [ + -73.56765050367694, + 45.492560280202284 + ], + [ + -73.5677794213865, + 45.49262188364245 + ], + [ + -73.56781916241786, + 45.49258006136105 + ], + [ + -73.56769087843276, + 45.49251875903776 + ] + ] + ] + }, + "id": 173347, + "properties": { + "name": "01044617", + "address": "rue Victor-Hugo (MTL) 1666", + "function": "1000", + "height": 9, + "year_of_construction": 1986 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -73.56765050367694, + 45.492560280202284 + ], + [ + -73.56761436875776, + 45.49259744179384 + ], + [ + -73.5676075694645, + 45.49260454199484 + ], + [ + -73.56773226889548, + 45.49266394156485 + ], + [ + -73.56773726906921, + 45.49266624130272 + ], + [ + -73.5677794213865, + 45.49262188364245 + ], + [ + -73.56765050367694, + 45.492560280202284 + ] + ] + ] + }, + "id": 173348, + "properties": { + "name": "01044619", + "address": "rue Victor-Hugo (MTL) 1670", + "function": "1000", + "height": 9, + "year_of_construction": 1986 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -73.56829026835214, + 45.492524742569145 + ], + [ + -73.56849646900322, + 45.49262354174874 + ], + [ + -73.56861067001111, + 45.492505541343576 + ], + [ + -73.56864076915663, + 45.492519941474434 + ], + [ + -73.56866246900178, + 45.49249754209202 + ], + [ + -73.56867696946317, + 45.49250454136644 + ], + [ + -73.56867726964143, + 45.49250414255471 + ], + [ + -73.56881486931461, + 45.492362042624144 + ], + [ + -73.56881686903772, + 45.492359941181455 + ], + [ + -73.5688004699483, + 45.49235084193039 + ], + [ + -73.56882097012145, + 45.4923320417195 + ], + [ + -73.56879846891101, + 45.49232034109352 + ], + [ + -73.56883736970825, + 45.492284841271946 + ], + [ + -73.56886806888434, + 45.492256240993704 + ], + [ + -73.56885337003277, + 45.49224914198001 + ], + [ + -73.56890226932418, + 45.49219894164121 + ], + [ + -73.56851866897392, + 45.49201434154299 + ], + [ + -73.56837326884313, + 45.492163841620254 + ], + [ + -73.56864696910176, + 45.49229554163243 + ], + [ + -73.5685268682051, + 45.49241904187041 + ], + [ + -73.56825396962694, + 45.49228824183907 + ], + [ + -73.56810906858335, + 45.49243794104013 + ], + [ + -73.56829026835214, + 45.492524742569145 + ] + ] + ] + }, + "id": 173403, + "properties": { + "name": "01044334", + "address": "rue Saint-Jacques (MTL) 1460", + "function": "1000", + "height": 15, + "year_of_construction": 1985 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -73.5683896684674, + 45.491800342137736 + ], + [ + -73.56838616878639, + 45.49180414157881 + ], + [ + -73.56850686988925, + 45.49185994152571 + ], + [ + -73.56851286844197, + 45.4918626410622 + ], + [ + -73.56855549071014, + 45.49181750806087 + ], + [ + -73.56842962331187, + 45.49175738300567 + ], + [ + -73.5683896684674, + 45.491800342137736 + ] + ] + ] + }, + "id": 174898, + "properties": { + "name": "01044590", + "address": "rue Victor-Hugo (MTL) 1600", + "function": "1000", + "height": 9, + "year_of_construction": 1986 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -73.5680637695714, + 45.49212884162544 + ], + [ + -73.56802228176146, + 45.49217205619571 + ], + [ + -73.56815668696326, + 45.49223626189717 + ], + [ + -73.56815766959974, + 45.49223524178655 + ], + [ + -73.56818746886172, + 45.49224944155107 + ], + [ + -73.56822816806918, + 45.49220694186927 + ], + [ + -73.5680637695714, + 45.49212884162544 + ] + ] + ] + }, + "id": 175785, + "properties": { + "name": "01044602", + "address": "rue Victor-Hugo (MTL) 1630", + "function": "1000", + "height": 12, + "year_of_construction": 1986 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -73.56850793693103, + 45.49167318076048 + ], + [ + -73.56846877951091, + 45.4917152818903 + ], + [ + -73.56859506290321, + 45.491775605518725 + ], + [ + -73.56863463503653, + 45.491733702062774 + ], + [ + -73.56850793693103, + 45.49167318076048 + ] + ] + ] + }, + "id": 175910, + "properties": { + "name": "01044586", + "address": "rue Victor-Hugo (MTL) 1590", + "function": "1000", + "height": 9, + "year_of_construction": 1986 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -73.56817543449134, + 45.49201384773851 + ], + [ + -73.56813497596143, + 45.49205532773507 + ], + [ + -73.56826745951075, + 45.492118613912375 + ], + [ + -73.56830763251781, + 45.49207699906335 + ], + [ + -73.56817543449134, + 45.49201384773851 + ] + ] + ] + }, + "id": 176056, + "properties": { + "name": "01044599", + "address": "rue Victor-Hugo (MTL) 1620", + "function": "1000", + "height": 8, + "year_of_construction": 1986 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -73.56772876855176, + 45.49247194194522 + ], + [ + -73.56773406949068, + 45.492474341387755 + ], + [ + -73.56773125185198, + 45.492477239659124 + ], + [ + -73.56785890467093, + 45.492538239964624 + ], + [ + -73.56789966910456, + 45.49249534173201 + ], + [ + -73.56776616865103, + 45.49243264153464 + ], + [ + -73.56772876855176, + 45.49247194194522 + ] + ] + ] + }, + "id": 176261, + "properties": { + "name": "01044613", + "address": "rue Victor-Hugo (MTL) 1656", + "function": "1000", + "height": 10, + "year_of_construction": 1986 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -73.56802228176146, + 45.49217205619571 + ], + [ + -73.56798225825526, + 45.492213743742184 + ], + [ + -73.56811660206223, + 45.49227791893211 + ], + [ + -73.56815668696326, + 45.49223626189717 + ], + [ + -73.56802228176146, + 45.49217205619571 + ] + ] + ] + }, + "id": 176293, + "properties": { + "name": "01044604", + "address": "rue Victor-Hugo (MTL) 1636", + "function": "1000", + "height": 12, + "year_of_construction": 1986 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -73.56790222258577, + 45.49229712328457 + ], + [ + -73.56785996900595, + 45.49234104192853 + ], + [ + -73.56799446861396, + 45.49240484193282 + ], + [ + -73.56803643080562, + 45.49236123475947 + ], + [ + -73.56790222258577, + 45.49229712328457 + ] + ] + ] + }, + "id": 176296, + "properties": { + "name": "01044611", + "address": "rue Victor-Hugo (MTL) 1650", + "function": "1000", + "height": 10, + "year_of_construction": 1986 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -73.56798225825526, + 45.492213743742184 + ], + [ + -73.56794223597048, + 45.4922554321734 + ], + [ + -73.56807651582375, + 45.49231957685336 + ], + [ + -73.56811660206223, + 45.49227791893211 + ], + [ + -73.56798225825526, + 45.492213743742184 + ] + ] + ] + }, + "id": 176298, + "properties": { + "name": "01044607", + "address": "rue Victor-Hugo (MTL) 1640", + "function": "1000", + "height": 12, + "year_of_construction": 1986 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -73.56742736898599, + 45.49184704208998 + ], + [ + -73.56761256873325, + 45.491896142437554 + ], + [ + -73.56766926915839, + 45.4917902412014 + ], + [ + -73.56766956853903, + 45.49179024192391 + ], + [ + -73.56792966911675, + 45.49183254222432 + ], + [ + -73.56793006788594, + 45.491831141828406 + ], + [ + -73.56794526884076, + 45.49174634219527 + ], + [ + -73.56794516904765, + 45.49174634225465 + ], + [ + -73.56753896905731, + 45.491638642248425 + ], + [ + -73.56742736898599, + 45.49184704208998 + ] + ] + ] + }, + "id": 176918, + "properties": { + "name": "01097185", + "address": "rue Victor-Hugo (MTL) 1591", + "function": "1000", + "height": 10, + "year_of_construction": 1987 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -73.56773125185198, + 45.492477239659124 + ], + [ + -73.56769087843276, + 45.49251875903776 + ], + [ + -73.56781916241786, + 45.49258006136105 + ], + [ + -73.56785890467093, + 45.492538239964624 + ], + [ + -73.56773125185198, + 45.492477239659124 + ] + ] + ] + }, + "id": 178164, + "properties": { + "name": "01044615", + "address": "rue Victor-Hugo (MTL) 1660", + "function": "1000", + "height": 9, + "year_of_construction": 1986 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -73.56846877951091, + 45.4917152818903 + ], + [ + -73.56842962331187, + 45.49175738300567 + ], + [ + -73.56855549071014, + 45.49181750806087 + ], + [ + -73.56859506290321, + 45.491775605518725 + ], + [ + -73.56846877951091, + 45.4917152818903 + ] + ] + ] + }, + "id": 179679, + "properties": { + "name": "01044588", + "address": "rue Victor-Hugo (MTL) 1596", + "function": "1000", + "height": 9, + "year_of_construction": 1986 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -73.56825635009473, + 45.49193088860213 + ], + [ + -73.56821589168355, + 45.491972368627906 + ], + [ + -73.5683477837006, + 45.4920353716151 + ], + [ + -73.56838787594006, + 45.49199371809223 + ], + [ + -73.56825635009473, + 45.49193088860213 + ] + ] + ] + }, + "id": 179789, + "properties": { + "name": "01044595", + "address": "rue Victor-Hugo (MTL) 1610", + "function": "1000", + "height": 8, + "year_of_construction": 1986 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -73.56821589168355, + 45.491972368627906 + ], + [ + -73.56817543449134, + 45.49201384773851 + ], + [ + -73.56830763251781, + 45.49207699906335 + ], + [ + -73.5683477837006, + 45.4920353716151 + ], + [ + -73.56821589168355, + 45.491972368627906 + ] + ] + ] + }, + "id": 181310, + "properties": { + "name": "01044597", + "address": "rue Victor-Hugo (MTL) 1616", + "function": "1000", + "height": 8, + "year_of_construction": 1986 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -73.56809506939487, + 45.49209624228538 + ], + [ + -73.56809246893268, + 45.4920988416879 + ], + [ + -73.56821287000538, + 45.49216124158406 + ], + [ + -73.56822186852654, + 45.49216584161625 + ], + [ + -73.56826745951075, + 45.492118613912375 + ], + [ + -73.56813497596143, + 45.49205532773507 + ], + [ + -73.56809506939487, + 45.49209624228538 + ] + ] + ] + }, + "id": 182393, + "properties": { + "name": "01044601", + "address": "rue Victor-Hugo (MTL) 1626", + "function": "1000", + "height": 8, + "year_of_construction": 1986 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -73.56790756893894, + 45.492291541967774 + ], + [ + -73.56790222258577, + 45.49229712328457 + ], + [ + -73.56803643080562, + 45.49236123475947 + ], + [ + -73.56807651582375, + 45.49231957685336 + ], + [ + -73.56794223597048, + 45.4922554321734 + ], + [ + -73.56790756893894, + 45.492291541967774 + ] + ] + ] + }, + "id": 182442, + "properties": { + "name": "01044609", + "address": "rue Victor-Hugo (MTL) 1646", + "function": "1000", + "height": 11, + "year_of_construction": 1986 + } + }, + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -73.56829706912258, + 45.49188914205178 + ], + [ + -73.56825635009473, + 45.49193088860213 + ], + [ + -73.56838787594006, + 45.49199371809223 + ], + [ + -73.56842846901456, + 45.49195154234486 + ], + [ + -73.56829706912258, + 45.49188914205178 + ] + ] + ] + }, + "id": 182546, + "properties": { + "name": "01044592", + "address": "rue Victor-Hugo (MTL) 1606", + "function": "1000", + "height": 8, + "year_of_construction": 1986 + } + } + ] +} \ No newline at end of file diff --git a/input_files/test_geojson.geojson b/input_files/test_geojson.geojson new file mode 100644 index 00000000..df9f0c4c --- /dev/null +++ b/input_files/test_geojson.geojson @@ -0,0 +1,55 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -73.58000127109773, + 45.49613461675315 + ], + [ + -73.57962787855432, + 45.496524875557746 + ], + [ + -73.57996357265695, + 45.49668114195629 + ], + [ + -73.57996427397713, + 45.496680342403664 + ], + [ + -73.58034707390021, + 45.49625804233725 + ], + [ + -73.58034697395713, + 45.496257942524835 + ], + [ + -73.58000127109773, + 45.49613461675315 + ] + ] + ] + }, + "id": 179764, + "properties": { + "name": "01119274", + "address": "rue Guy (MTL) 2157", + "function": "Mixed use", + "mixed_type_1": "commercial", + "mixed_type_1_percentage": 50, + "mixed_type_2": "6000", + "mixed_type_2_percentage": 50, + "height": 62, + "year_of_construction": 1954 + } + } + ] +} \ No newline at end of file diff --git a/main.py b/main.py index 4af9643a..edce68d2 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,3 @@ -from scripts.geojson_creator import process_geojson from pathlib import Path import subprocess from scripts.ep_run_enrich import energy_plus_workflow @@ -8,58 +7,80 @@ from hub.imports.construction_factory import ConstructionFactory from hub.imports.usage_factory import UsageFactory from hub.imports.weather_factory import WeatherFactory from hub.imports.results_factory import ResultFactory -from scripts.energy_system_analysis_report import EnergySystemAnalysisReport +from scripts.energy_system_retrofit_report import EnergySystemRetrofitReport +from scripts.geojson_creator import process_geojson from scripts import random_assignation from hub.imports.energy_systems_factory import EnergySystemsFactory from scripts.energy_system_sizing import SystemSizing -from scripts.energy_system_retrofit_results import system_results, new_system_results +from scripts.solar_angles import CitySolarAngles +from scripts.pv_sizing_and_simulation import PVSizingSimulation +from scripts.energy_system_retrofit_results import consumption_data, cost_data from scripts.energy_system_sizing_and_simulation_factory import EnergySystemsSimulationFactory from scripts.costs.cost import Cost -from scripts.costs.constants import SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV, SYSTEM_RETROFIT_AND_PV +from scripts.costs.constants import SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV, SYSTEM_RETROFIT_AND_PV, CURRENT_STATUS import hub.helpers.constants as cte from hub.exports.exports_factory import ExportsFactory +from scripts.pv_feasibility import pv_feasibility +import matplotlib.pyplot as plt +import numpy as np # Specify the GeoJSON file path -# geojson_file = process_geojson(x=-73.5953602192335, y=45.492414530022515, diff=0.001) -file_path = (Path(__file__).parent / 'input_files' / 'output_buildings.geojson') -# Specify the output path for the PDF file +data = {} +input_files_path = (Path(__file__).parent / 'input_files') +input_files_path.mkdir(parents=True, exist_ok=True) +# geojson_file = process_geojson(x=-73.58001358793511, y=45.496445294438715, diff=0.0001) +geojson_file_path = input_files_path / 'test_geojson.geojson' output_path = (Path(__file__).parent / 'out_files').resolve() -# Create city object from GeoJSON file -city = GeometryFactory('geojson', - path=file_path, +output_path.mkdir(parents=True, exist_ok=True) +energy_plus_output_path = output_path / 'energy_plus_outputs' +energy_plus_output_path.mkdir(parents=True, exist_ok=True) +simulation_results_path = (Path(__file__).parent / 'out_files' / 'simulation_results').resolve() +simulation_results_path.mkdir(parents=True, exist_ok=True) +sra_output_path = output_path / 'sra_outputs' +sra_output_path.mkdir(parents=True, exist_ok=True) +cost_analysis_output_path = output_path / 'cost_analysis' +cost_analysis_output_path.mkdir(parents=True, exist_ok=True) +city = GeometryFactory(file_type='geojson', + path=geojson_file_path, height_field='height', year_of_construction_field='year_of_construction', function_field='function', function_to_hub=Dictionaries().montreal_function_to_hub_function).city -# Enrich city data ConstructionFactory('nrcan', city).enrich() - UsageFactory('nrcan', city).enrich() WeatherFactory('epw', city).enrich() -energy_plus_workflow(city) -random_assignation.call_random(city.buildings, random_assignation.residential_systems_percentage) -EnergySystemsFactory('montreal_custom', city).enrich() -SystemSizing(city.buildings).montreal_custom() -current_system = new_system_results(city.buildings) -random_assignation.call_random(city.buildings, random_assignation.residential_new_systems_percentage) -EnergySystemsFactory('montreal_future', city).enrich() -for building in city.buildings: - EnergySystemsSimulationFactory('archetype1', building=building, output_path=output_path).enrich() - print(building.energy_consumption_breakdown[cte.ELECTRICITY][cte.COOLING] + - building.energy_consumption_breakdown[cte.ELECTRICITY][cte.HEATING] + - building.energy_consumption_breakdown[cte.ELECTRICITY][cte.DOMESTIC_HOT_WATER]) -new_system = new_system_results(city.buildings) -# EnergySystemAnalysisReport(city, output_path).create_report(current_system, new_system) -for building in city.buildings: - costs = Cost(building=building, retrofit_scenario=SYSTEM_RETROFIT_AND_PV).life_cycle - costs.to_csv(output_path / f'{building.name}_lcc.csv') - (costs.loc['global_operational_costs', f'Scenario {SYSTEM_RETROFIT_AND_PV}']. - to_csv(output_path / f'{building.name}_op.csv')) - costs.loc['global_capital_costs', f'Scenario {SYSTEM_RETROFIT_AND_PV}'].to_csv( - output_path / f'{building.name}_cc.csv') - costs.loc['global_maintenance_costs', f'Scenario {SYSTEM_RETROFIT_AND_PV}'].to_csv( - output_path / f'{building.name}_m.csv') - - - - - +energy_plus_workflow(city, energy_plus_output_path) +data[f'{city.buildings[0].function}'] = city.buildings[0].heating_demand[cte.YEAR][0] / 3.6e9 +city.buildings[0].function = cte.COMMERCIAL +ConstructionFactory('nrcan', city).enrich() +UsageFactory('nrcan', city).enrich() +energy_plus_workflow(city, energy_plus_output_path) +data[f'{city.buildings[0].function}'] = city.buildings[0].heating_demand[cte.YEAR][0] / 3.6e9 +city.buildings[0].function = cte.MEDIUM_OFFICE +ConstructionFactory('nrcan', city).enrich() +UsageFactory('nrcan', city).enrich() +energy_plus_workflow(city, energy_plus_output_path) +data[f'{city.buildings[0].function}'] = city.buildings[0].heating_demand[cte.YEAR][0] / 3.6e9 +categories = list(data.keys()) +values = list(data.values()) +# Plotting +fig, ax = plt.subplots(figsize=(10, 6), dpi=96) +fig.suptitle('Impact of different usages on yearly heating demand', fontsize=16, weight='bold', alpha=.8) +ax.bar(categories, values, color=['#2196f3', '#ff5a5f', '#4caf50'], width=0.6, zorder=2) +ax.grid(which="major", axis='x', color='#DAD8D7', alpha=0.5, zorder=1) +ax.grid(which="major", axis='y', color='#DAD8D7', alpha=0.5, zorder=1) +ax.set_xlabel('Building Type', fontsize=12, labelpad=10) +ax.set_ylabel('Energy Consumption (MWh)', fontsize=14, labelpad=10) +ax.yaxis.set_major_locator(plt.MaxNLocator(integer=True)) +ax.set_xticks(np.arange(len(categories))) +ax.set_xticklabels(categories, rotation=45, ha='right') +ax.bar_label(ax.containers[0], padding=3, color='black', fontsize=12, rotation=0) +ax.spines[['top', 'left', 'bottom']].set_visible(False) +ax.spines['right'].set_linewidth(1.1) +# Set a white background +fig.patch.set_facecolor('white') +# Adjust the margins around the plot area +plt.subplots_adjust(left=0.1, right=0.9, top=0.85, bottom=0.25) +# Save the plot +plt.savefig('plot_nrcan.png', bbox_inches='tight') +plt.close() +print('test') \ No newline at end of file diff --git a/plot_nrcan.png b/plot_nrcan.png new file mode 100644 index 00000000..c780de3f Binary files /dev/null and b/plot_nrcan.png differ diff --git a/pv_assessment.py b/pv_assessment.py new file mode 100644 index 00000000..7978df3a --- /dev/null +++ b/pv_assessment.py @@ -0,0 +1,73 @@ +import pandas as pd +from scripts.geojson_creator import process_geojson +from pathlib import Path +import subprocess +from hub.imports.geometry_factory import GeometryFactory +from hub.helpers.dictionaries import Dictionaries +from hub.imports.construction_factory import ConstructionFactory +from hub.imports.usage_factory import UsageFactory +from hub.imports.weather_factory import WeatherFactory +from hub.imports.results_factory import ResultFactory +from scripts.solar_angles import CitySolarAngles +from scripts.ep_run_enrich import energy_plus_workflow +import hub.helpers.constants as cte +from hub.exports.exports_factory import ExportsFactory +from scripts.pv_sizing_and_simulation import PVSizingSimulation +# Specify the GeoJSON file path +geojson_file = process_geojson(x=-73.5681295982132, y=45.49218262677643, diff=0.0005) +file_path = (Path(__file__).parent / 'input_files' / 'output_buildings.geojson') +# Specify the output path for the PDF file +output_path = (Path(__file__).parent / 'out_files').resolve() +# Create city object from GeoJSON file +city = GeometryFactory('geojson', + path=file_path, + height_field='height', + year_of_construction_field='year_of_construction', + function_field='function', + function_to_hub=Dictionaries().montreal_function_to_hub_function).city +# Enrich city data +ConstructionFactory('nrcan', city).enrich() + +UsageFactory('nrcan', city).enrich() +WeatherFactory('epw', city).enrich() +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() +energy_plus_workflow(city) +solar_angles = CitySolarAngles(city.name, + city.latitude, + city.longitude, + tilt_angle=45, + surface_azimuth_angle=180).calculate +df = pd.DataFrame() +df.index = ['yearly lighting (kWh)', 'yearly appliance (kWh)', 'yearly heating (kWh)', 'yearly cooling (kWh)', + 'yearly dhw (kWh)', 'roof area (m2)', 'used area for pv (m2)', 'number of panels', 'pv production (kWh)'] +for building in city.buildings: + ghi = [x / cte.WATTS_HOUR_TO_JULES for x in building.roofs[0].global_irradiance[cte.HOUR]] + pv_sizing_simulation = PVSizingSimulation(building, + solar_angles, + tilt_angle=45, + module_height=1, + module_width=2, + ghi=ghi) + pv_sizing_simulation.pv_output() + yearly_lighting = building.lighting_electrical_demand[cte.YEAR][0] / 1000 + yearly_appliance = building.appliances_electrical_demand[cte.YEAR][0] / 1000 + yearly_heating = building.heating_demand[cte.YEAR][0] / (3.6e6 * 3) + yearly_cooling = building.cooling_demand[cte.YEAR][0] / (3.6e6 * 4.5) + yearly_dhw = building.domestic_hot_water_heat_demand[cte.YEAR][0] / 1000 + roof_area = building.roofs[0].perimeter_area + used_roof = pv_sizing_simulation.available_space() + number_of_pv_panels = pv_sizing_simulation.total_number_of_panels + yearly_pv = building.onsite_electrical_production[cte.YEAR][0] / 1000 + df[f'{building.name}'] = [yearly_lighting, yearly_appliance, yearly_heating, yearly_cooling, yearly_dhw, roof_area, + used_roof, number_of_pv_panels, yearly_pv] + +df.to_csv(output_path / 'pv.csv') + + + + + + diff --git a/scripts/costs/capital_costs.py b/scripts/costs/capital_costs.py index 68d751d8..832aeb7d 100644 --- a/scripts/costs/capital_costs.py +++ b/scripts/costs/capital_costs.py @@ -12,7 +12,8 @@ import numpy_financial as npf from hub.city_model_structure.building import Building import hub.helpers.constants as cte 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.constants import (SKIN_RETROFIT, SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV, + SYSTEM_RETROFIT_AND_PV, CURRENT_STATUS, PV, SYSTEM_RETROFIT) from scripts.costs.cost_base import CostBase @@ -31,12 +32,13 @@ class CapitalCosts(CostBase): 'B3010_opaque_roof', 'B1010_superstructure', 'D2010_photovoltaic_system', - 'D3020_heat_and_cooling_generating_systems', - 'D3040_distribution_systems', - 'D3050_other_hvac_ahu', - 'D3060_storage_systems', + 'D3020_simultaneous_heat_and_cooling_generating_systems', + 'D3030_heating_systems', + 'D3040_cooling_systems', + 'D3050_distribution_systems', + 'D3060_other_hvac_ahu', + 'D3070_storage_systems', 'D40_dhw', - 'D5020_lighting_and_branch_wiring' ], dtype='float' ) @@ -45,12 +47,13 @@ class CapitalCosts(CostBase): self._yearly_capital_costs.loc[0, 'B3010_opaque_roof'] = 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, 'D3060_storage_systems'] = 0 + self._yearly_capital_costs.loc[0, 'D3020_simultaneous_heat_and_cooling_generating_systems'] = 0 + self._yearly_capital_costs.loc[0, 'D3030_heating_systems'] = 0 + self._yearly_capital_costs.loc[0, 'D3040_cooling_systems'] = 0 + self._yearly_capital_costs.loc[0, 'D3050_distribution_systems'] = 0 + self._yearly_capital_costs.loc[0, 'D3060_other_hvac_ahu'] = 0 + self._yearly_capital_costs.loc[0, 'D3070_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, @@ -70,12 +73,14 @@ class CapitalCosts(CostBase): for roof in self._building.roofs: self._surface_pv += roof.solid_polygon.area * roof.solar_collectors_area_reduction_factor + for roof in self._building.roofs: + if roof.installed_solar_collector_area is not None: + self._surface_pv += roof.installed_solar_collector_area + else: + 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_capital_cost() + self.energy_system_capital_cost() self.skin_yearly_capital_costs() self.yearly_energy_system_costs() self.yearly_incomes() @@ -106,10 +111,11 @@ class CapitalCosts(CostBase): 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 + if self._configuration.retrofit_scenario not in (SYSTEM_RETROFIT_AND_PV, CURRENT_STATUS, PV, SYSTEM_RETROFIT): + 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 @@ -147,21 +153,22 @@ class CapitalCosts(CostBase): 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] + system_components, component_categories, component_sizes = self.system_components() capital_cost_heating_and_cooling_equipment = 0 + capital_cost_heating_equipment = 0 + capital_cost_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': + if component_categories[i] == 'multi_generation': capital_cost_heating_and_cooling_equipment += chapter.item(component).initial_investment[0] * component_sizes[i] + elif component_categories[i] == 'heating': + capital_cost_heating_equipment += chapter.item(component).initial_investment[0] * component_sizes[i] + elif component_categories[i] == 'cooling': + capital_cost_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] @@ -171,26 +178,37 @@ class CapitalCosts(CostBase): 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) + if self._configuration.retrofit_scenario in (SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV, SYSTEM_RETROFIT_AND_PV, PV): + self._yearly_capital_costs.loc[0, 'D2010_photovoltaic_system'] = capital_cost_pv + if (self._configuration.retrofit_scenario in + (SYSTEM_RETROFIT_AND_PV, SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV, SYSTEM_RETROFIT)): + self._yearly_capital_costs.loc[0, 'D3020_simultaneous_heat_and_cooling_generating_systems'] = ( + capital_cost_heating_and_cooling_equipment * self._own_capital) + self._yearly_capital_costs.loc[0, 'D3030_heating_systems'] = ( + capital_cost_heating_equipment * self._own_capital) + self._yearly_capital_costs.loc[0, 'D3040_cooling_systems'] = ( + capital_cost_cooling_equipment * self._own_capital) + self._yearly_capital_costs.loc[0, 'D3050_distribution_systems'] = ( + capital_cost_distribution_equipment * self._own_capital) + self._yearly_capital_costs.loc[0, 'D3070_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) + 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_heating_equipment, + capital_cost_distribution_equipment, capital_cost_cooling_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] + system_components, component_categories, component_sizes = self.system_components() + pv = False + for energy_system in self._building.energy_systems: + for generation_system in energy_system.generation_systems: + if generation_system.system_type == cte.PHOTOVOLTAIC: + pv = True 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'] = ( @@ -200,65 +218,90 @@ class CapitalCosts(CostBase): system_investment_costs[0] * self._configuration.percentage_credit ) ) - self._yearly_capital_costs.loc[year, 'D3020_heat_and_cooling_generating_systems'] = ( + self._yearly_capital_costs.loc[year, 'D3020_simultaneous_heat_and_cooling_generating_systems'] = ( -npf.pmt( self._configuration.interest_rate, self._configuration.credit_years, system_investment_costs[1] * self._configuration.percentage_credit ) ) - self._yearly_capital_costs.loc[year, 'D3040_distribution_systems'] = ( + self._yearly_capital_costs.loc[year, 'D3030_heating_systems'] = ( -npf.pmt( self._configuration.interest_rate, self._configuration.credit_years, system_investment_costs[2] * self._configuration.percentage_credit ) ) - self._yearly_capital_costs.loc[year, 'D3060_storage_systems'] = ( + self._yearly_capital_costs.loc[year, 'D3040_cooling_systems'] = ( -npf.pmt( self._configuration.interest_rate, self._configuration.credit_years, system_investment_costs[3] * self._configuration.percentage_credit ) ) - self._yearly_capital_costs.loc[year, 'D40_dhw'] = ( + self._yearly_capital_costs.loc[year, 'D3050_distribution_systems'] = ( -npf.pmt( self._configuration.interest_rate, self._configuration.credit_years, system_investment_costs[4] * self._configuration.percentage_credit ) ) - # 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): + self._yearly_capital_costs.loc[year, 'D3070_storage_systems'] = ( + -npf.pmt( + self._configuration.interest_rate, + self._configuration.credit_years, + system_investment_costs[5] * self._configuration.percentage_credit + ) + ) + self._yearly_capital_costs.loc[year, 'D40_dhw'] = ( + -npf.pmt( + self._configuration.interest_rate, + self._configuration.credit_years, + system_investment_costs[6] * self._configuration.percentage_credit + ) + ) + if self._configuration.retrofit_scenario not in (SKIN_RETROFIT, PV): + 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] == 'multi_generation': + reposition_cost_heating_and_cooling_equipment = (chapter.item(component).reposition[0] * + component_sizes[i] * costs_increase) + self._yearly_capital_costs.loc[year, 'D3020_simultaneous_heat_and_cooling_generating_systems'] += ( + reposition_cost_heating_and_cooling_equipment) + elif component_categories[i] == 'heating': + reposition_cost_heating_equipment = (chapter.item(component).reposition[0] * + component_sizes[i] * costs_increase) + self._yearly_capital_costs.loc[year, 'D3030_heating_systems'] += ( + reposition_cost_heating_equipment) + elif component_categories[i] == 'cooling': + reposition_cost_cooling_equipment = (chapter.item(component).reposition[0] * + component_sizes[i] * costs_increase) + self._yearly_capital_costs.loc[year, 'D3040_cooling_systems'] += ( + reposition_cost_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, 'D3050_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, 'D3070_storage_systems'] += reposition_cost_energy_storage_equipment + if self._configuration.retrofit_scenario == CURRENT_STATUS and pv: + 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 + ) + elif self._configuration.retrofit_scenario in (PV, SYSTEM_RETROFIT_AND_PV, + SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV): 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 ) - 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 = [] @@ -283,8 +326,11 @@ class CapitalCosts(CostBase): 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') + elif cte.HEATING in demand_types: + if cte.COOLING in demand_types and generation_system.fuel_type == cte.ELECTRICITY: + component_categories.append('multi_generation') + else: + component_categories.append('heating') sizes.append(installed_capacity) if generation_system.system_type == cte.HEAT_PUMP: item_type = self.heat_pump_type(generation_system) @@ -293,11 +339,18 @@ class CapitalCosts(CostBase): 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: + if cooling_capacity > heating_capacity: system_components.append('D302090_template_cooling') else: system_components.append('D302010_template_heat') - + elif cte.COOLING in demand_types: + component_categories.append('cooling') + 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) + else: + system_components.append('D302090_template_cooling') if generation_system.energy_storage_systems is not None: energy_storage_systems = generation_system.energy_storage_systems for storage_system in energy_storage_systems: @@ -308,7 +361,7 @@ class CapitalCosts(CostBase): 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) + sizes.append(self._building.cooling_peak_load[cte.YEAR][0] / 1000) system_components.append('D3040_distribution_systems') return system_components, component_categories, sizes diff --git a/scripts/costs/configuration.py b/scripts/costs/configuration.py index f868da79..3d5b9485 100644 --- a/scripts/costs/configuration.py +++ b/scripts/costs/configuration.py @@ -28,7 +28,8 @@ class Configuration: factories_handler, retrofit_scenario, fuel_type, - dictionary + dictionary, + fuel_tariffs ): self._number_of_years = number_of_years self._percentage_credit = percentage_credit @@ -45,6 +46,7 @@ class Configuration: self._retrofit_scenario = retrofit_scenario self._fuel_type = fuel_type self._dictionary = dictionary + self._fuel_tariffs = fuel_tariffs @property def number_of_years(self): @@ -227,3 +229,10 @@ class Configuration: Get hub function to cost function dictionary """ return self._dictionary + + @property + def fuel_tariffs(self): + """ + Get fuel tariffs + """ + return self._fuel_tariffs diff --git a/scripts/costs/constants.py b/scripts/costs/constants.py index cbf11e57..87372d3d 100644 --- a/scripts/costs/constants.py +++ b/scripts/costs/constants.py @@ -11,9 +11,13 @@ CURRENT_STATUS = 0 SKIN_RETROFIT = 1 SYSTEM_RETROFIT_AND_PV = 2 SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV = 3 +PV = 4 +SYSTEM_RETROFIT = 5 RETROFITTING_SCENARIOS = [ CURRENT_STATUS, SKIN_RETROFIT, SYSTEM_RETROFIT_AND_PV, - SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV + SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV, + PV, + SYSTEM_RETROFIT ] diff --git a/scripts/costs/cost.py b/scripts/costs/cost.py index 996c4724..91a13034 100644 --- a/scripts/costs/cost.py +++ b/scripts/costs/cost.py @@ -40,7 +40,10 @@ class Cost: retrofitting_year_construction=2020, factories_handler='montreal_new', retrofit_scenario=CURRENT_STATUS, - dictionary=None): + dictionary=None, + fuel_tariffs=None): + if fuel_tariffs is None: + fuel_tariffs = ['Electricity-D', 'Gas-Energir'] if dictionary is None: dictionary = Dictionaries().hub_function_to_montreal_custom_costs_function self._building = building @@ -57,7 +60,8 @@ class Cost: factories_handler, retrofit_scenario, fuel_type, - dictionary) + dictionary, + fuel_tariffs) @property def building(self) -> Building: @@ -89,12 +93,13 @@ class Cost: global_capital_costs['B1010_superstructure'] ) df_capital_costs_systems = ( - 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['D3020_simultaneous_heat_and_cooling_generating_systems'] + + global_capital_costs['D3030_heating_systems'] + + global_capital_costs['D3040_cooling_systems'] + + global_capital_costs['D3050_distribution_systems'] + + global_capital_costs['D3060_other_hvac_ahu'] + + global_capital_costs['D3070_storage_systems'] + global_capital_costs['D40_dhw'] + - global_capital_costs['D5020_lighting_and_branch_wiring'] + global_capital_costs['D2010_photovoltaic_system'] ) diff --git a/scripts/costs/peak_load.py b/scripts/costs/peak_load.py index 108f748a..422f563b 100644 --- a/scripts/costs/peak_load.py +++ b/scripts/costs/peak_load.py @@ -45,7 +45,7 @@ class PeakLoad: conditioning_peak[i] = self._building.heating_peak_load[cte.MONTH][i] * heating else: conditioning_peak[i] = self._building.cooling_peak_load[cte.MONTH][i] * cooling - monthly_electricity_peak[i] += 0.8 * conditioning_peak[i] / 3600 + monthly_electricity_peak[i] += 0.8 * conditioning_peak[i] electricity_peak_load_results = pd.DataFrame( monthly_electricity_peak, diff --git a/scripts/costs/total_maintenance_costs.py b/scripts/costs/total_maintenance_costs.py index 81a88de8..7a11b9b6 100644 --- a/scripts/costs/total_maintenance_costs.py +++ b/scripts/costs/total_maintenance_costs.py @@ -25,6 +25,7 @@ class TotalMaintenanceCosts(CostBase): columns=[ 'Heating_maintenance', 'Cooling_maintenance', + 'DHW_maintenance', 'PV_maintenance' ], dtype='float' @@ -39,15 +40,77 @@ class TotalMaintenanceCosts(CostBase): archetype = self._archetype # todo: change area pv when the variable exists roof_area = 0 - for roof in building.roofs: - roof_area += roof.solid_polygon.area - surface_pv = roof_area * 0.5 + surface_pv = 0 + for roof in self._building.roofs: + if roof.installed_solar_collector_area is not None: + surface_pv += roof.installed_solar_collector_area + else: + surface_pv = roof_area * 0.5 - peak_heating = building.heating_peak_load[cte.YEAR][0] / 3.6e6 - peak_cooling = building.cooling_peak_load[cte.YEAR][0] / 3.6e6 + energy_systems = building.energy_systems + maintenance_heating_0 = 0 + maintenance_cooling_0 = 0 + maintenance_dhw_0 = 0 + heating_equipments = {} + cooling_equipments = {} + dhw_equipments = {} + for energy_system in energy_systems: + if cte.COOLING in energy_system.demand_types: + for generation_system in energy_system.generation_systems: + if generation_system.fuel_type == cte.ELECTRICITY: + if generation_system.system_type == cte.HEAT_PUMP and generation_system.source_medium == cte.AIR: + cooling_equipments['air_source_heat_pump'] = generation_system.nominal_cooling_output / 1000 + elif generation_system.system_type == cte.HEAT_PUMP and generation_system.source_medium == cte.GROUND: + cooling_equipments['ground_source_heat_pump'] = generation_system.nominal_cooling_output / 1000 + elif generation_system.system_type == cte.HEAT_PUMP and generation_system.source_medium == cte.WATER: + cooling_equipments['water_source_heat_pump'] = generation_system.nominal_cooling_output / 1000 + else: + cooling_equipments['general_cooling_equipment'] = generation_system.nominal_cooling_output / 1000 + if cte.HEATING in energy_system.demand_types: + for generation_system in energy_system.generation_systems: + if generation_system.system_type == cte.HEAT_PUMP and generation_system.source_medium == cte.AIR: + heating_equipments['air_source_heat_pump'] = generation_system.nominal_heat_output / 1000 + elif generation_system.system_type == cte.HEAT_PUMP and generation_system.source_medium == cte.GROUND: + heating_equipments['ground_source_heat_pump'] = generation_system.nominal_heat_output / 1000 + elif generation_system.system_type == cte.HEAT_PUMP and generation_system.source_medium == cte.WATER: + heating_equipments['water_source_heat_pump'] = generation_system.nominal_heat_output / 1000 + elif generation_system.system_type == cte.BOILER and generation_system.fuel_type == cte.GAS: + heating_equipments['gas_boiler'] = generation_system.nominal_heat_output / 1000 + elif generation_system.system_type == cte.BOILER and generation_system.fuel_type == cte.ELECTRICITY: + heating_equipments['electric_boiler'] = generation_system.nominal_heat_output / 1000 + else: + heating_equipments['general_heating_equipment'] = generation_system.nominal_heat_output / 1000 + if cte.DOMESTIC_HOT_WATER in energy_system.demand_types and cte.HEATING not in energy_system.demand_types: + for generation_system in energy_system.generation_systems: + if generation_system.system_type == cte.HEAT_PUMP and generation_system.source_medium == cte.AIR: + dhw_equipments['air_source_heat_pump'] = generation_system.nominal_heat_output / 1000 + elif generation_system.system_type == cte.HEAT_PUMP and generation_system.source_medium == cte.GROUND: + dhw_equipments['ground_source_heat_pump'] = generation_system.nominal_heat_output / 1000 + elif generation_system.system_type == cte.HEAT_PUMP and generation_system.source_medium == cte.WATER: + dhw_equipments['water_source_heat_pump'] = generation_system.nominal_heat_output / 1000 + elif generation_system.system_type == cte.BOILER and generation_system.fuel_type == cte.GAS: + dhw_equipments['gas_boiler'] = generation_system.nominal_heat_output / 1000 + elif generation_system.system_type == cte.BOILER and generation_system.fuel_type == cte.ELECTRICITY: + dhw_equipments['electric_boiler'] = generation_system.nominal_heat_output / 1000 + else: + dhw_equipments['general_heating_equipment'] = generation_system.nominal_heat_output / 1000 + + + for heating_equipment in heating_equipments: + component = self.search_hvac_equipment(heating_equipment) + maintenance_cost = component.maintenance[0] + maintenance_heating_0 += (heating_equipments[heating_equipment] * maintenance_cost) + + for cooling_equipment in cooling_equipments: + component = self.search_hvac_equipment(cooling_equipment) + maintenance_cost = component.maintenance[0] + maintenance_cooling_0 += (cooling_equipments[cooling_equipment] * maintenance_cost) + + for dhw_equipment in dhw_equipments: + component = self.search_hvac_equipment(dhw_equipment) + maintenance_cost = component.maintenance[0] + maintenance_dhw_0 += (dhw_equipments[dhw_equipment] * maintenance_cost) - maintenance_heating_0 = peak_heating * archetype.operational_cost.maintenance_heating - maintenance_cooling_0 = peak_cooling * archetype.operational_cost.maintenance_cooling maintenance_pv_0 = surface_pv * archetype.operational_cost.maintenance_pv for year in range(1, self._configuration.number_of_years + 1): @@ -58,8 +121,18 @@ class TotalMaintenanceCosts(CostBase): self._yearly_maintenance_costs.loc[year, 'Cooling_maintenance'] = ( maintenance_cooling_0 * costs_increase ) + self._yearly_maintenance_costs.loc[year, 'DHW_maintenance'] = ( + maintenance_dhw_0 * costs_increase + ) self._yearly_maintenance_costs.loc[year, 'PV_maintenance'] = ( maintenance_pv_0 * costs_increase ) self._yearly_maintenance_costs.fillna(0, inplace=True) return self._yearly_maintenance_costs + + def search_hvac_equipment(self, equipment_type): + for component in self._archetype.operational_cost.maintenance_hvac: + if component.type == equipment_type: + return component + + diff --git a/scripts/costs/total_operational_costs.py b/scripts/costs/total_operational_costs.py index 6b0c1e27..60ed54a9 100644 --- a/scripts/costs/total_operational_costs.py +++ b/scripts/costs/total_operational_costs.py @@ -42,48 +42,68 @@ class TotalOperationalCosts(CostBase): factor = total_floor_area / 80 else: factor = 1 - total_electricity_consumption = sum(self._building.energy_consumption_breakdown[cte.ELECTRICITY].values()) + total_electricity_consumption = sum(self._building.energy_consumption_breakdown[cte.ELECTRICITY].values()) / 3600 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 - fuels = archetype.operational_cost.fuels - for fuel in fuels: - if fuel.type in fuel_consumption_breakdown.keys(): - if fuel.type == cte.ELECTRICITY: + for system_fuel in self._configuration.fuel_type: + fuel = None + for fuel_tariff in self._configuration.fuel_tariffs: + if system_fuel in fuel_tariff: + fuel = self.search_fuel(system_fuel, fuel_tariff) + if fuel.type == cte.ELECTRICITY: + if fuel.variable.rate_type == 'fixed': variable_electricity_cost_year_0 = ( - total_electricity_consumption * fuel.variable[0] / 1000 + total_electricity_consumption * float(fuel.variable.values[0]) / 1000 ) - 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 + hourly_electricity_consumption = self.hourly_fuel_consumption_profile(fuel.type) + hourly_electricity_price_profile = fuel.variable.values * len(hourly_electricity_consumption) + hourly_electricity_price = [hourly_electricity_consumption[i] / 1000 * hourly_electricity_price_profile[i] + for i in range(len(hourly_electricity_consumption))] + variable_electricity_cost_year_0 = sum(hourly_electricity_price) + 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: - conversion_factor = fuel.density[0] + 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] + if fuel.variable.rate_type == 'fixed': variable_cost_fuel = ( - ((sum(fuel_consumption_breakdown[fuel.type].values()) * 3600)/(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) + (sum(fuel_consumption_breakdown[fuel.type].values()) / ( + 1e6 * fuel.lower_heating_value[0] * conversion_factor)) * fuel.variable.values[0]) + + else: + hourly_fuel_consumption = self.hourly_fuel_consumption_profile(fuel.type) + hourly_fuel_price_profile = fuel.variable.values * len(hourly_fuel_consumption) + hourly_fuel_price = [hourly_fuel_consumption[i] / ( + 1e6 * fuel.lower_heating_value[0] * conversion_factor) * hourly_fuel_price_profile[i] + for i in range(len(hourly_fuel_consumption))] + variable_cost_fuel = sum(hourly_fuel_price) + + 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 @@ -102,3 +122,116 @@ class TotalOperationalCosts(CostBase): return columns_list + def search_fuel(self, system_fuel, tariff): + fuels = self._archetype.operational_cost.fuels + for fuel in fuels: + if system_fuel == fuel.type and tariff == fuel.variable.name: + return fuel + raise KeyError(f'fuel {system_fuel} with {tariff} tariff not found') + + + def hourly_fuel_consumption_profile(self, fuel_type): + hourly_fuel_consumption = [] + energy_systems = self._building.energy_systems + if fuel_type == cte.ELECTRICITY: + appliance = self._building.appliances_electrical_demand[cte.HOUR] + lighting = self._building.lighting_electrical_demand[cte.HOUR] + elec_heating = 0 + elec_cooling = 0 + elec_dhw = 0 + if cte.HEATING in self._building.energy_consumption_breakdown[cte.ELECTRICITY]: + elec_heating = 1 + if cte.COOLING in self._building.energy_consumption_breakdown[cte.ELECTRICITY]: + elec_cooling = 1 + if cte.DOMESTIC_HOT_WATER in self._building.energy_consumption_breakdown[cte.ELECTRICITY]: + elec_dhw = 1 + heating = None + cooling = None + dhw = None + + if elec_heating == 1: + for energy_system in energy_systems: + if cte.HEATING in energy_system.demand_types: + for generation_system in energy_system.generation_systems: + if generation_system.fuel_type == cte.ELECTRICITY: + if cte.HEATING in generation_system.energy_consumption: + heating = generation_system.energy_consumption[cte.HEATING][cte.HOUR] + else: + if len(energy_system.generation_systems) > 1: + heating = [x / 2 for x in self._building.heating_consumption[cte.HOUR]] + else: + heating = self._building.heating_consumption[cte.HOUR] + + if elec_dhw == 1: + for energy_system in energy_systems: + if cte.DOMESTIC_HOT_WATER in energy_system.demand_types: + for generation_system in energy_system.generation_systems: + if generation_system.fuel_type == cte.ELECTRICITY: + if cte.DOMESTIC_HOT_WATER in generation_system.energy_consumption: + dhw = generation_system.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.HOUR] + else: + if len(energy_system.generation_systems) > 1: + dhw = [x / 2 for x in self._building.domestic_hot_water_consumption[cte.HOUR]] + else: + dhw = self._building.domestic_hot_water_consumption[cte.HOUR] + + if elec_cooling == 1: + for energy_system in energy_systems: + if cte.COOLING in energy_system.demand_types: + for generation_system in energy_system.generation_systems: + if cte.COOLING in generation_system.energy_consumption: + cooling = generation_system.energy_consumption[cte.COOLING][cte.HOUR] + else: + if len(energy_system.generation_systems) > 1: + cooling = [x / 2 for x in self._building.cooling_consumption[cte.HOUR]] + else: + cooling = self._building.cooling_consumption[cte.HOUR] + + for i in range(len(self._building.heating_demand[cte.HOUR])): + hourly = 0 + hourly += appliance[i] / 3600 + hourly += lighting[i] / 3600 + if heating is not None: + hourly += heating[i] / 3600 + if cooling is not None: + hourly += cooling[i] / 3600 + if dhw is not None: + hourly += dhw[i] / 3600 + hourly_fuel_consumption.append(hourly) + else: + heating = None + dhw = None + if cte.HEATING in self._building.energy_consumption_breakdown[fuel_type]: + for energy_system in energy_systems: + if cte.HEATING in energy_system.demand_types: + for generation_system in energy_system.generation_systems: + if cte.HEATING in generation_system.energy_consumption: + heating = generation_system.energy_consumption[cte.HEATING][cte.HOUR] + else: + if len(energy_system.generation_systems) > 1: + heating = [x / 2 for x in self._building.heating_consumption[cte.HOUR]] + else: + heating = self._building.heating_consumption[cte.HOUR] + if cte.DOMESTIC_HOT_WATER in self._building.energy_consumption_breakdown[fuel_type]: + for energy_system in energy_systems: + if cte.DOMESTIC_HOT_WATER in energy_system.demand_types: + for generation_system in energy_system.generation_systems: + if cte.DOMESTIC_HOT_WATER in generation_system.energy_consumption: + dhw = generation_system.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.HOUR] + else: + if len(energy_system.generation_systems) > 1: + dhw = [x / 2 for x in self._building.domestic_hot_water_consumption[cte.HOUR]] + else: + dhw = self._building.domestic_hot_water_consumption[cte.HOUR] + + for i in range(len(self._building.heating_demand[cte.HOUR])): + hourly = 0 + if heating is not None: + hourly += heating[i] / 3600 + if dhw is not None: + hourly += dhw[i] / 3600 + hourly_fuel_consumption.append(hourly) + return hourly_fuel_consumption + + + diff --git a/scripts/costs/total_operational_incomes.py b/scripts/costs/total_operational_incomes.py index 2a110761..f3a9f8ac 100644 --- a/scripts/costs/total_operational_incomes.py +++ b/scripts/costs/total_operational_incomes.py @@ -33,14 +33,12 @@ class TotalOperationalIncomes(CostBase): onsite_electricity_production = 0 else: onsite_electricity_production = building.onsite_electrical_production[cte.YEAR][0] - for year in range(1, self._configuration.number_of_years + 1): price_increase_electricity = math.pow(1 + self._configuration.electricity_price_index, year) - # todo: check the adequate assignation of price. Pilar - price_export = archetype.income.electricity_export * cte.WATTS_HOUR_TO_JULES * 1000 # to account for unit change + price_export = archetype.income.electricity_export # to account for unit change self._yearly_operational_incomes.loc[year, 'Incomes electricity'] = ( - onsite_electricity_production * price_export * price_increase_electricity + (onsite_electricity_production / 3.6e6) * price_export * price_increase_electricity ) self._yearly_operational_incomes.fillna(0, inplace=True) - return self._yearly_operational_incomes + return self._yearly_operational_incomes \ No newline at end of file diff --git a/scripts/energy_system_analysis_report.py b/scripts/energy_system_analysis_report.py deleted file mode 100644 index 16620fc6..00000000 --- a/scripts/energy_system_analysis_report.py +++ /dev/null @@ -1,338 +0,0 @@ -import os -import hub.helpers.constants as cte -import matplotlib.pyplot as plt -import random -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 - self.output_path = output_path - self.content = [] - self.report = LatexReport('energy_system_analysis_report.tex') - - def building_energy_info(self): - - table_data = [ - ["Building Name", "Year of Construction", "function", "Yearly Heating Demand (MWh)", - "Yearly Cooling Demand (MWh)", "Yearly DHW Demand (MWh)", "Yearly Electricity Demand (MWh)"] - ] - intensity_table_data = [["Building Name", "Total Floor Area m2", "Heating Demand Intensity kWh/m2", - "Cooling Demand Intensity kWh/m2", "Electricity Intensity kWh/m2"]] - - for building in self.city.buildings: - total_floor_area = 0 - for zone in building.thermal_zones_from_internal_zones: - total_floor_area += zone.total_floor_area - building_data = [ - building.name, - str(building.year_of_construction), - building.function, - str(format(building.heating_demand[cte.YEAR][0] / 3.6e9, '.2f')), - str(format(building.cooling_demand[cte.YEAR][0] / 3.6e9, '.2f')), - str(format(building.domestic_hot_water_heat_demand[cte.YEAR][0] / 1e6, '.2f')), - str(format( - (building.lighting_electrical_demand[cte.YEAR][0] + building.appliances_electrical_demand[cte.YEAR][0]) - / 1e6, '.2f')), - ] - intensity_data = [ - building.name, - str(format(total_floor_area, '.2f')), - str(format(building.heating_demand[cte.YEAR][0] / (3.6e6 * total_floor_area), '.2f')), - str(format(building.cooling_demand[cte.YEAR][0] / (3.6e6 * total_floor_area), '.2f')), - str(format( - (building.lighting_electrical_demand[cte.YEAR][0] + building.appliances_electrical_demand[cte.YEAR][0]) / - (1e3 * total_floor_area), '.2f')) - ] - table_data.append(building_data) - intensity_table_data.append(intensity_data) - - self.report.add_table(table_data, caption='City Buildings Energy Demands') - self.report.add_table(intensity_table_data, caption='Energy Intensity Information') - - def base_case_charts(self): - save_directory = self.output_path - - def autolabel(bars, ax): - for bar in bars: - height = bar.get_height() - ax.annotate('{:.1f}'.format(height), - xy=(bar.get_x() + bar.get_width() / 2, height), - xytext=(0, 3), # 3 points vertical offset - textcoords="offset points", - ha='center', va='bottom') - - def create_hvac_demand_chart(building_names, yearly_heating_demand, yearly_cooling_demand): - fig, ax = plt.subplots() - bar_width = 0.35 - index = range(len(building_names)) - - bars1 = ax.bar(index, yearly_heating_demand, bar_width, label='Yearly Heating Demand (MWh)') - bars2 = ax.bar([i + bar_width for i in index], yearly_cooling_demand, bar_width, - label='Yearly Cooling Demand (MWh)') - - ax.set_xlabel('Building Name') - ax.set_ylabel('Energy Demand (MWh)') - ax.set_title('Yearly HVAC Demands') - ax.set_xticks([i + bar_width / 2 for i in index]) - ax.set_xticklabels(building_names, rotation=45, ha='right') - ax.legend() - autolabel(bars1, ax) - autolabel(bars2, ax) - fig.tight_layout() - plt.savefig(save_directory / 'hvac_demand_chart.jpg') - plt.close() - - def create_bar_chart(title, ylabel, data, filename, bar_color=None): - fig, ax = plt.subplots() - bar_width = 0.35 - index = range(len(building_names)) - - if bar_color is None: - # Generate a random color - bar_color = random.choice(list(mcolors.CSS4_COLORS.values())) - - bars = ax.bar(index, data, bar_width, label=ylabel, color=bar_color) - - ax.set_xlabel('Building Name') - ax.set_ylabel('Energy Demand (MWh)') - ax.set_title(title) - ax.set_xticks([i + bar_width / 2 for i in index]) - ax.set_xticklabels(building_names, rotation=45, ha='right') - ax.legend() - autolabel(bars, ax) - fig.tight_layout() - plt.savefig(save_directory / filename) - plt.close() - - building_names = [building.name for building in self.city.buildings] - yearly_heating_demand = [building.heating_demand[cte.YEAR][0] / 3.6e9 for building in self.city.buildings] - yearly_cooling_demand = [building.cooling_demand[cte.YEAR][0] / 3.6e9 for building in self.city.buildings] - yearly_dhw_demand = [building.domestic_hot_water_heat_demand[cte.YEAR][0] / 1e6 for building in - self.city.buildings] - yearly_electricity_demand = [(building.lighting_electrical_demand[cte.YEAR][0] + - building.appliances_electrical_demand[cte.YEAR][0]) / 1e6 for building in - self.city.buildings] - - create_hvac_demand_chart(building_names, yearly_heating_demand, yearly_cooling_demand) - create_bar_chart('Yearly DHW Demands', 'Energy Demand (MWh)', yearly_dhw_demand, 'dhw_demand_chart.jpg', ) - create_bar_chart('Yearly Electricity Demands', 'Energy Demand (MWh)', yearly_electricity_demand, - 'electricity_demand_chart.jpg') - - def maximum_monthly_hvac_chart(self): - save_directory = self.output_path - months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', - 'November', 'December'] - for building in self.city.buildings: - maximum_monthly_heating_load = [] - maximum_monthly_cooling_load = [] - fig, axs = plt.subplots(1, 2, figsize=(12, 6)) # Create a figure with 2 subplots side by side - for demand in building.heating_peak_load[cte.MONTH]: - maximum_monthly_heating_load.append(demand / 3.6e6) - for demand in building.cooling_peak_load[cte.MONTH]: - maximum_monthly_cooling_load.append(demand / 3.6e6) - - # Plot maximum monthly heating load - axs[0].bar(months, maximum_monthly_heating_load, color='red') # Plot on the first subplot - axs[0].set_title('Maximum Monthly Heating Load') - axs[0].set_xlabel('Month') - axs[0].set_ylabel('Load (kWh)') - axs[0].tick_params(axis='x', rotation=45) - - # Plot maximum monthly cooling load - axs[1].bar(months, maximum_monthly_cooling_load, color='blue') # Plot on the second subplot - axs[1].set_title('Maximum Monthly Cooling Load') - axs[1].set_xlabel('Month') - axs[1].set_ylabel('Load (kWh)') - axs[1].tick_params(axis='x', rotation=45) - - plt.tight_layout() # Adjust layout to prevent overlapping - plt.savefig(save_directory / f'{building.name}_monthly_maximum_hvac_loads.jpg') - plt.close() - - def load_duration_curves(self): - save_directory = self.output_path - for building in self.city.buildings: - heating_demand = [demand / 3.6e6 for demand in building.heating_demand[cte.HOUR]] - cooling_demand = [demand / 3.6e6 for demand in building.cooling_demand[cte.HOUR]] - heating_demand_sorted = sorted(heating_demand, reverse=True) - cooling_demand_sorted = sorted(cooling_demand, reverse=True) - - plt.style.use('ggplot') - - # Create figure and axis objects with 1 row and 2 columns - fig, axs = plt.subplots(1, 2, figsize=(12, 6)) - - # Plot sorted heating demand - axs[0].plot(heating_demand_sorted, color='red', linewidth=2, label='Heating Demand') - axs[0].set_xlabel('Hour', fontsize=14) - axs[0].set_ylabel('Heating Demand (kWh)', fontsize=14) - axs[0].set_title('Heating Load Duration Curve', fontsize=16) - axs[0].grid(True) - axs[0].legend(loc='upper right', fontsize=12) - - # Plot sorted cooling demand - axs[1].plot(cooling_demand_sorted, color='blue', linewidth=2, label='Cooling Demand') - axs[1].set_xlabel('Hour', fontsize=14) - axs[1].set_ylabel('Cooling Demand (kWh)', fontsize=14) - axs[1].set_title('Cooling Load Duration Curve', fontsize=16) - axs[1].grid(True) - axs[1].legend(loc='upper right', fontsize=12) - - # Adjust layout - plt.tight_layout() - plt.savefig(save_directory / f'{building.name}_load_duration_curve.jpg') - plt.close() - - def individual_building_info(self, building): - table_data = [ - ["Maximum Monthly HVAC Demands", - f"\\includegraphics[width=1\\linewidth]{{{building.name}_monthly_maximum_hvac_loads.jpg}}"], - ["Load Duration Curve", f"\\includegraphics[width=1\\linewidth]{{{building.name}_load_duration_curve.jpg}}"], - ] - - self.report.add_table(table_data, caption=f'{building.name} Information', first_column_width=1.5) - - 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 Equipments'] - current_system_cooling = current_system[f'{building_name}']['Cooling Equipments'] - current_system_dhw = current_system[f'{building_name}']['DHW Equipments'] - 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 Equipments'] - new_system_cooling = new_system[f'{building_name}']['Cooling Equipments'] - new_system_dhw = new_system[f'{building_name}']['DHW Equipments'] - 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", current_system_archetype, new_system_archetype], - ["Heating Equipments", current_system_heating, new_system_heating], - ["Cooling Equipments", current_system_cooling, new_system_cooling], - ["DHW Equipments", 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') - - def building_fuel_consumption_breakdown(self, building): - save_directory = self.output_path - # Initialize variables to store fuel consumption breakdown - fuel_breakdown = { - "Heating": {"Gas": 0, "Electricity": 0}, - "Domestic Hot Water": {"Gas": 0, "Electricity": 0}, - "Cooling": {"Electricity": 0}, - "Appliance": building.appliances_electrical_demand[cte.YEAR][0] / 1e6, - "Lighting": building.lighting_electrical_demand[cte.YEAR][0] / 1e6 - } - - # Iterate through energy systems of the building - for energy_system in building.energy_systems: - for demand_type in energy_system.demand_types: - 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']] - if fuel_breakdown['Heating']['Electricity'] > 0: - electricity_labels.append('Heating') - electricity_sizes.append(fuel_breakdown['Heating']['Electricity']) - if fuel_breakdown['Cooling']['Electricity'] > 0: - electricity_labels.append('Cooling') - electricity_sizes.append(fuel_breakdown['Cooling']['Electricity']) - if fuel_breakdown['Domestic Hot Water']['Electricity'] > 0: - electricity_labels.append('Domestic Hot Water') - electricity_sizes.append(fuel_breakdown['Domestic Hot Water']['Electricity']) - - # Data for bar chart - gas_labels = ['Heating', 'Domestic Hot Water'] - gas_sizes = [fuel_breakdown['Heating']['Gas'], fuel_breakdown['Domestic Hot Water']['Gas']] - - # Set the style - plt.style.use('ggplot') - - # Create plot grid - fig, axs = plt.subplots(1, 2, figsize=(12, 6)) - - # Plot pie chart for electricity consumption breakdown - colors = cm.get_cmap('tab20c', len(electricity_labels)) - axs[0].pie(electricity_sizes, labels=electricity_labels, - autopct=lambda pct: f"{pct:.1f}%\n({pct / 100 * sum(electricity_sizes):.2f})", - startangle=90, colors=[colors(i) for i in range(len(electricity_labels))]) - axs[0].set_title('Electricity Consumption Breakdown') - - # Plot bar chart for natural gas consumption breakdown - colors = cm.get_cmap('Paired', len(gas_labels)) - axs[1].bar(gas_labels, gas_sizes, color=[colors(i) for i in range(len(gas_labels))]) - axs[1].set_ylabel('Consumption (MWh)') - axs[1].set_title('Natural Gas Consumption Breakdown') - - # Add grid to bar chart - axs[1].grid(axis='y', linestyle='--', alpha=0.7) - - # Add a title to the entire figure - plt.suptitle('Building Energy Consumption Breakdown', fontsize=16, fontweight='bold') - - # Adjust layout - plt.tight_layout() - - # Save the plot as a high-quality image - plt.savefig(save_directory / f'{building.name}_energy_consumption_breakdown.png', dpi=300) - plt.close() - - def create_report(self, current_system, new_system): - os.chdir(self.output_path) - self.report.add_section('Current Status') - self.building_energy_info() - self.base_case_charts() - self.report.add_image('hvac_demand_chart.jpg', caption='Yearly HVAC Demands') - self.report.add_image('dhw_demand_chart.jpg', caption='Yearly DHW Demands') - self.report.add_image('electricity_demand_chart.jpg', caption='Yearly Electricity Demands') - self.maximum_monthly_hvac_chart() - self.load_duration_curves() - for building in self.city.buildings: - self.individual_building_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') - self.report.save_report() - self.report.compile_to_pdf() diff --git a/scripts/energy_system_retrofit_report.py b/scripts/energy_system_retrofit_report.py new file mode 100644 index 00000000..738437c9 --- /dev/null +++ b/scripts/energy_system_retrofit_report.py @@ -0,0 +1,596 @@ +import os +import hub.helpers.constants as cte +import matplotlib.pyplot as plt +from matplotlib import cm +from scripts.report_creation import LatexReport +from matplotlib.ticker import MaxNLocator +import numpy as np +from pathlib import Path +import glob + + +class EnergySystemRetrofitReport: + def __init__(self, city, output_path, retrofit_scenario, current_status_energy_consumption_data, + retrofitted_energy_consumption_data, current_status_lcc_data, retrofitted_lcc_data): + self.city = city + self.current_status_data = current_status_energy_consumption_data + self.retrofitted_data = retrofitted_energy_consumption_data + self.current_status_lcc = current_status_lcc_data + self.retrofitted_lcc = retrofitted_lcc_data + self.output_path = output_path + self.content = [] + self.retrofit_scenario = retrofit_scenario + self.report = LatexReport('energy_system_retrofit_report', + 'Energy System Retrofit Report', self.retrofit_scenario, output_path) + self.system_schemas_path = (Path(__file__).parent.parent / 'hub' / 'data' / 'energy_systems' / 'schemas') + self.charts_path = Path(output_path) / 'charts' + self.charts_path.mkdir(parents=True, exist_ok=True) + files = glob.glob(f'{self.charts_path}/*') + for file in files: + os.remove(file) + + def building_energy_info(self): + table_data = [ + ["Building Name", "Year of Construction", "function", "Yearly Heating Demand (MWh)", + "Yearly Cooling Demand (MWh)", "Yearly DHW Demand (MWh)", "Yearly Electricity Demand (MWh)"] + ] + intensity_table_data = [["Building Name", "Total Floor Area $m^2$", "Heating Demand Intensity kWh/ $m^2$", + "Cooling Demand Intensity kWh/ $m^2$", "Electricity Intensity kWh/ $m^2$"]] + peak_load_data = [["Building Name", "Heating Peak Load (kW)", "Cooling Peak Load (kW)", + "Domestic Hot Water Peak Load (kW)"]] + + for building in self.city.buildings: + total_floor_area = 0 + for zone in building.thermal_zones_from_internal_zones: + total_floor_area += zone.total_floor_area + building_data = [ + building.name, + str(building.year_of_construction), + building.function, + str(format(building.heating_demand[cte.YEAR][0] / 3.6e9, '.2f')), + str(format(building.cooling_demand[cte.YEAR][0] / 3.6e9, '.2f')), + str(format(building.domestic_hot_water_heat_demand[cte.YEAR][0] / 3.6e9, '.2f')), + str(format( + (building.lighting_electrical_demand[cte.YEAR][0] + building.appliances_electrical_demand[cte.YEAR][0]) + / 3.6e9, '.2f')), + ] + intensity_data = [ + building.name, + str(format(total_floor_area, '.2f')), + str(format(building.heating_demand[cte.YEAR][0] / (3.6e6 * total_floor_area), '.2f')), + str(format(building.cooling_demand[cte.YEAR][0] / (3.6e6 * total_floor_area), '.2f')), + str(format( + (building.lighting_electrical_demand[cte.YEAR][0] + building.appliances_electrical_demand[cte.YEAR][0]) / + (3.6e6 * total_floor_area), '.2f')) + ] + peak_data = [ + building.name, + str(format(building.heating_peak_load[cte.YEAR][0] / 1000, '.2f')), + str(format(building.cooling_peak_load[cte.YEAR][0] / 1000, '.2f')), + str(format( + (building.lighting_electrical_demand[cte.YEAR][0] + building.appliances_electrical_demand[cte.YEAR][0]) / + (3.6e6 * total_floor_area), '.2f')) + ] + table_data.append(building_data) + intensity_table_data.append(intensity_data) + peak_load_data.append(peak_data) + + self.report.add_table(table_data, caption='Buildings Energy Consumption Data') + self.report.add_table(intensity_table_data, caption='Buildings Energy Use Intensity Data') + self.report.add_table(peak_load_data, caption='Buildings Peak Load Data') + + def plot_monthly_energy_demands(self, data, file_name, title): + # Data preparation + months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] + demands = { + 'Heating': ('heating', '#2196f3'), + 'Cooling': ('cooling', '#ff5a5f'), + 'DHW': ('dhw', '#4caf50'), + 'Electricity': ('lighting_appliance', '#ffc107') + } + + # Helper function for plotting + def plot_bar_chart(ax, demand_type, color, ylabel, title): + values = data[demand_type] + ax.bar(months, values, color=color, width=0.6, zorder=2) + ax.grid(which="major", axis='x', color='#DAD8D7', alpha=0.5, zorder=1) + ax.grid(which="major", axis='y', color='#DAD8D7', alpha=0.5, zorder=1) + ax.set_ylabel(ylabel, fontsize=14, labelpad=10) + ax.set_title(title, fontsize=14, weight='bold', alpha=.8, pad=40) + ax.xaxis.set_major_locator(MaxNLocator(integer=True)) + ax.yaxis.set_major_locator(MaxNLocator(integer=True)) + ax.set_xticks(np.arange(len(months))) + ax.set_xticklabels(months, rotation=45, ha='right') + ax.bar_label(ax.containers[0], padding=3, color='black', fontsize=12, rotation=90) + ax.spines[['top', 'left', 'bottom']].set_visible(False) + ax.spines['right'].set_linewidth(1.1) + average_value = np.mean(values) + ax.axhline(y=average_value, color='grey', linewidth=2, linestyle='--') + ax.text(len(months) - 1, average_value, f'Average = {average_value:.1f} kWh', ha='right', va='bottom', + color='grey') + + # Plotting + fig, axs = plt.subplots(4, 1, figsize=(20, 16), dpi=96) + fig.suptitle(title, fontsize=16, weight='bold', alpha=.8) + + plot_bar_chart(axs[0], 'heating', demands['Heating'][1], 'Heating Demand (kWh)', 'Monthly Heating Demand') + plot_bar_chart(axs[1], 'cooling', demands['Cooling'][1], 'Cooling Demand (kWh)', 'Monthly Cooling Demand') + plot_bar_chart(axs[2], 'dhw', demands['DHW'][1], 'DHW Demand (kWh)', 'Monthly DHW Demand') + plot_bar_chart(axs[3], 'lighting_appliance', demands['Electricity'][1], 'Electricity Demand (kWh)', + 'Monthly Electricity Demand') + + # Set a white background + fig.patch.set_facecolor('white') + + # Adjust the margins around the plot area + plt.subplots_adjust(left=0.05, right=0.95, top=0.9, bottom=0.1, hspace=0.5) + + # Save the plot + chart_path = self.charts_path / f'{file_name}.png' + plt.savefig(chart_path, bbox_inches='tight') + plt.close() + + return chart_path + + def plot_monthly_energy_consumption(self, data, file_name, title): + # Data preparation + months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] + consumptions = { + 'Heating': ('heating', '#2196f3', 'Heating Consumption (kWh)', 'Monthly Energy Consumption for Heating'), + 'Cooling': ('cooling', '#ff5a5f', 'Cooling Consumption (kWh)', 'Monthly Energy Consumption for Cooling'), + 'DHW': ('dhw', '#4caf50', 'DHW Consumption (kWh)', 'Monthly DHW Consumption') + } + + # Helper function for plotting + def plot_bar_chart(ax, consumption_type, color, ylabel, title): + values = data[consumption_type] + ax.bar(months, values, color=color, width=0.6, zorder=2) + ax.grid(which="major", axis='x', color='#DAD8D7', alpha=0.5, zorder=1) + ax.grid(which="major", axis='y', color='#DAD8D7', alpha=0.5, zorder=1) + ax.set_xlabel('Month', fontsize=12, labelpad=10) + ax.set_ylabel(ylabel, fontsize=14, labelpad=10) + ax.set_title(title, fontsize=14, weight='bold', alpha=.8, pad=40) + ax.xaxis.set_major_locator(MaxNLocator(integer=True)) + ax.yaxis.set_major_locator(MaxNLocator(integer=True)) + ax.set_xticks(np.arange(len(months))) + ax.set_xticklabels(months, rotation=45, ha='right') + ax.bar_label(ax.containers[0], padding=3, color='black', fontsize=12, rotation=90) + ax.spines[['top', 'left', 'bottom']].set_visible(False) + ax.spines['right'].set_linewidth(1.1) + average_value = np.mean(values) + ax.axhline(y=average_value, color='grey', linewidth=2, linestyle='--') + ax.text(len(months) - 1, average_value, f'Average = {average_value:.1f} kWh', ha='right', va='bottom', + color='grey') + + # Plotting + fig, axs = plt.subplots(3, 1, figsize=(20, 15), dpi=96) + fig.suptitle(title, fontsize=16, weight='bold', alpha=.8) + + plot_bar_chart(axs[0], 'heating', consumptions['Heating'][1], consumptions['Heating'][2], + consumptions['Heating'][3]) + plot_bar_chart(axs[1], 'cooling', consumptions['Cooling'][1], consumptions['Cooling'][2], + consumptions['Cooling'][3]) + plot_bar_chart(axs[2], 'dhw', consumptions['DHW'][1], consumptions['DHW'][2], consumptions['DHW'][3]) + + # Set a white background + fig.patch.set_facecolor('white') + + # Adjust the margins around the plot area + plt.subplots_adjust(left=0.05, right=0.95, top=0.9, bottom=0.1, wspace=0.3, hspace=0.5) + + # Save the plot + chart_path = self.charts_path / f'{file_name}.png' + plt.savefig(chart_path, bbox_inches='tight') + plt.close() + + return chart_path + + def fuel_consumption_breakdown(self, file_name, data): + fuel_consumption_breakdown = {} + for building in self.city.buildings: + for key, breakdown in data[f'{building.name}']['energy_consumption_breakdown'].items(): + if key not in fuel_consumption_breakdown: + fuel_consumption_breakdown[key] = {sector: 0 for sector in breakdown} + for sector, value in breakdown.items(): + if sector in fuel_consumption_breakdown[key]: + fuel_consumption_breakdown[key][sector] += value / 3.6e6 + else: + fuel_consumption_breakdown[key][sector] = value / 3.6e6 + + plt.style.use('ggplot') + num_keys = len(fuel_consumption_breakdown) + fig, axs = plt.subplots(1 if num_keys <= 2 else num_keys, min(num_keys, 2), figsize=(12, 5)) + axs = axs if num_keys > 1 else [axs] # Ensure axs is always iterable + + for i, (fuel, breakdown) in enumerate(fuel_consumption_breakdown.items()): + labels = breakdown.keys() + values = breakdown.values() + colors = cm.get_cmap('tab20c', len(labels)) + ax = axs[i] if num_keys > 1 else axs[0] + ax.pie(values, labels=labels, + autopct=lambda pct: f"{pct:.1f}%\n({pct / 100 * sum(values):.2f})", + startangle=90, colors=[colors(j) for j in range(len(labels))]) + ax.set_title(f'{fuel} Consumption Breakdown') + + plt.suptitle('City Energy Consumption Breakdown', fontsize=16, fontweight='bold') + plt.tight_layout(rect=[0, 0, 1, 0.95]) # Adjust layout to fit the suptitle + + chart_path = self.charts_path / f'{file_name}.png' + plt.savefig(chart_path, dpi=300) + plt.close() + return chart_path + + def energy_system_archetype_schematic(self): + energy_system_archetypes = {} + for building in self.city.buildings: + if building.energy_systems_archetype_name not in energy_system_archetypes: + energy_system_archetypes[building.energy_systems_archetype_name] = [building.name] + else: + energy_system_archetypes[building.energy_systems_archetype_name].append(building.name) + + text = "" + items = "" + for archetype, buildings in energy_system_archetypes.items(): + buildings_str = ", ".join(buildings) + text += f"Figure 4 shows the schematic of the proposed energy system for buildings {buildings_str}.\n" + if archetype in ['PV+4Pipe+DHW', 'PV+ASHP+GasBoiler+TES']: + text += "This energy system archetype is formed of the following systems: \par" + items = ['Rooftop Photovoltaic System: The rooftop PV system is tied to the grid and in case there is surplus ' + 'energy, it is sold to Hydro-Quebec through their Net-Meterin program.', + '4-Pipe HVAC System: This systems includes a 4-pipe heat pump capable of generating heat and cooling ' + 'at the same time, a natural gas boiler as the auxiliary heating system, and a hot water storage tank.' + 'The temperature inside the tank is kept between 40-55 C. The cooling demand is totally supplied by ' + 'the heat pump unit.', + 'Domestic Hot Water Heat Pump System: This system is in charge of supplying domestic hot water demand.' + 'The heat pump is connected to a thermal storage tank with electric resistance heating coil inside it.' + ' The temperature inside the tank should always remain above 60 C.'] + + self.report.add_text(text) + self.report.add_itemize(items=items) + schema_path = self.system_schemas_path / f'{archetype}.jpg' + self.report.add_image(str(schema_path).replace('\\', '/'), + f'Proposed energy system for buildings {buildings_str}') + + def plot_monthly_radiation(self): + months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] + monthly_roof_radiation = [] + for i in range(len(months)): + tilted_radiation = 0 + for building in self.city.buildings: + tilted_radiation += (building.roofs[0].global_irradiance_tilted[cte.MONTH][i] / + (cte.WATTS_HOUR_TO_JULES * 1000)) + monthly_roof_radiation.append(tilted_radiation) + + def plot_bar_chart(ax, months, values, color, ylabel, title): + ax.bar(months, values, color=color, width=0.6, zorder=2) + ax.grid(which="major", axis='x', color='#DAD8D7', alpha=0.5, zorder=1) + ax.grid(which="major", axis='y', color='#DAD8D7', alpha=0.5, zorder=1) + ax.set_xlabel('Month', fontsize=12, labelpad=10) + ax.set_ylabel(ylabel, fontsize=14, labelpad=10) + ax.set_title(title, fontsize=14, weight='bold', alpha=.8, pad=40) + ax.xaxis.set_major_locator(MaxNLocator(integer=True)) + ax.yaxis.set_major_locator(MaxNLocator(integer=True)) + ax.set_xticks(np.arange(len(months))) + ax.set_xticklabels(months, rotation=45, ha='right') + ax.bar_label(ax.containers[0], padding=3, color='black', fontsize=12, rotation=90) + ax.spines[['top', 'left', 'bottom']].set_visible(False) + ax.spines['right'].set_linewidth(1.1) + average_value = np.mean(values) + ax.axhline(y=average_value, color='grey', linewidth=2, linestyle='--') + ax.text(len(months) - 1, average_value, f'Average = {average_value:.1f} kWh', ha='right', va='bottom', + color='grey') + + # Plotting the bar chart + fig, ax = plt.subplots(figsize=(15, 8), dpi=96) + plot_bar_chart(ax, months, monthly_roof_radiation, '#ffc107', 'Tilted Roof Radiation (kWh / m2)', + 'Monthly Tilted Roof Radiation') + + # Set a white background + fig.patch.set_facecolor('white') + + # Adjust the margins around the plot area + plt.subplots_adjust(left=0.1, right=0.95, top=0.9, bottom=0.1) + + # Save the plot + chart_path = self.charts_path / 'monthly_tilted_roof_radiation.png' + plt.savefig(chart_path, bbox_inches='tight') + plt.close() + return chart_path + + def energy_consumption_comparison(self, current_status_data, retrofitted_data, file_name, title): + months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] + consumptions = { + 'Heating': ('heating', '#2196f3', 'Heating Consumption (kWh)', 'Monthly Energy Consumption for Heating'), + 'Cooling': ('cooling', '#ff5a5f', 'Cooling Consumption (kWh)', 'Monthly Energy Consumption for Cooling'), + 'DHW': ('dhw', '#4caf50', 'DHW Consumption (kWh)', 'Monthly DHW Consumption') + } + + # Helper function for plotting + def plot_double_bar_chart(ax, consumption_type, color, ylabel, title): + current_values = current_status_data[consumption_type] + retrofitted_values = retrofitted_data[consumption_type] + bar_width = 0.35 + index = np.arange(len(months)) + + ax.bar(index, current_values, bar_width, label='Current Status', color=color, alpha=0.7, zorder=2) + ax.bar(index + bar_width, retrofitted_values, bar_width, label='Retrofitted', color=color, hatch='/', zorder=2) + + ax.grid(which="major", axis='x', color='#DAD8D7', alpha=0.5, zorder=1) + ax.grid(which="major", axis='y', color='#DAD8D7', alpha=0.5, zorder=1) + ax.set_xlabel('Month', fontsize=12, labelpad=10) + ax.set_ylabel(ylabel, fontsize=14, labelpad=10) + ax.set_title(title, fontsize=14, weight='bold', alpha=.8, pad=40) + ax.set_xticks(index + bar_width / 2) + ax.set_xticklabels(months, rotation=45, ha='right') + ax.legend() + + # Adding bar labels + ax.bar_label(ax.containers[0], padding=3, color='black', fontsize=12, rotation=90) + ax.bar_label(ax.containers[1], padding=3, color='black', fontsize=12, rotation=90) + + ax.spines[['top', 'left', 'bottom']].set_visible(False) + ax.spines['right'].set_linewidth(1.1) + + # Plotting + fig, axs = plt.subplots(3, 1, figsize=(20, 25), dpi=96) + fig.suptitle(title, fontsize=16, weight='bold', alpha=.8) + + plot_double_bar_chart(axs[0], 'heating', consumptions['Heating'][1], consumptions['Heating'][2], + consumptions['Heating'][3]) + plot_double_bar_chart(axs[1], 'cooling', consumptions['Cooling'][1], consumptions['Cooling'][2], + consumptions['Cooling'][3]) + plot_double_bar_chart(axs[2], 'dhw', consumptions['DHW'][1], consumptions['DHW'][2], consumptions['DHW'][3]) + + # Set a white background + fig.patch.set_facecolor('white') + + # Adjust the margins around the plot area + plt.subplots_adjust(left=0.05, right=0.95, top=0.9, bottom=0.1, wspace=0.3, hspace=0.5) + + # Save the plot + chart_path = self.charts_path / f'{file_name}.png' + plt.savefig(chart_path, bbox_inches='tight') + plt.close() + + return chart_path + + def yearly_consumption_comparison(self): + current_total_consumption = round(self.current_status_data['total_consumption'], 2) + retrofitted_total_consumption = round(self.retrofitted_data['total_consumption'], 2) + text = ( + f'The total yearly energy consumption before and after the retrofit are {current_total_consumption} MWh and ' + f'{retrofitted_total_consumption} MWh, respectively.') + if retrofitted_total_consumption < current_total_consumption: + change = str(round((current_total_consumption - retrofitted_total_consumption) * 100 / current_total_consumption, + 2)) + text += f'Therefore, the total yearly energy consumption decreased by {change} \%.' + else: + change = str(round((retrofitted_total_consumption - current_total_consumption) * 100 / + retrofitted_total_consumption, 2)) + text += f'Therefore, the total yearly energy consumption increased by {change} \%. \par' + self.report.add_text(text) + + def pv_system(self): + self.report.add_text('The first step in PV assessments is evaluating the potential of buildings for installing ' + 'rooftop PV system. The benchmark value used for this evaluation is the mean yearly solar ' + 'incident in Montreal. According to Hydro-Quebec, the mean annual incident in Montreal is 1350' + 'kWh/m2. Therefore, any building with rooftop annual global horizontal radiation of less than ' + '1080 kWh/m2 is considered to be infeasible. Table 4 shows the yearly horizontal radiation on ' + 'buildings roofs. \par') + radiation_data = [ + ["Building Name", "Roof Area $m^2$", "Function", "Rooftop Annual Global Horizontal Radiation kWh/ $m^2$"] + ] + pv_feasible_buildings = [] + for building in self.city.buildings: + if building.roofs[0].global_irradiance[cte.YEAR][0] > 1080: + pv_feasible_buildings.append(building.name) + data = [building.name, str(format(building.roofs[0].perimeter_area, '.2f')), building.function, + str(format(building.roofs[0].global_irradiance[cte.YEAR][0] / (cte.WATTS_HOUR_TO_JULES * 1000), '.2f'))] + radiation_data.append(data) + + self.report.add_table(radiation_data, + caption='Buildings Roof Characteristics') + + if len(pv_feasible_buildings) == len(self.city.buildings): + buildings_str = 'all' + else: + buildings_str = ", ".join(pv_feasible_buildings) + self.report.add_text(f"From the table it can be seen that {buildings_str} buildings are good candidates to have " + f"rooftop PV system. The next step is calculating the amount of solar radiation on a tilted " + f"surface. Figure 5 shows the total monthly solar radiation on a surface with the tilt angle " + f"of 45 degrees on the roofs of those buildings that are identified to have rooftop PV system." + f"\par") + tilted_radiation = self.plot_monthly_radiation() + self.report.add_image(str(tilted_radiation).replace('\\', '/'), + caption='Total Monthly Solar Radiation on Buildings Roofs on a 45 Degrees Tilted Surface', + placement='H') + self.report.add_text('The first step in sizing the PV system is to find the available roof area. ' + 'Few considerations need to be made here. The considerations include space for maintenance ' + 'crew, space for mechanical equipment, and orientation correction factor to make sure all ' + 'the panel are truly facing south. After all these considerations, the minimum distance ' + 'between the panels to avoid shading throughout the year is found. Table 5 shows the number of' + 'panles on each buildings roof, yearly PV production, total electricity consumption, and self ' + 'consumption. \par') + + pv_output_table = [['Building Name', 'Total Surface Area of PV Panels ($m^2$)', + 'Total Solar Incident on PV Modules (MWh)', 'Yearly PV production (MWh)']] + + for building in self.city.buildings: + if building.name in pv_feasible_buildings: + pv_data = [] + pv_data.append(building.name) + yearly_solar_incident = (building.roofs[0].global_irradiance_tilted[cte.YEAR][0] * + building.roofs[0].installed_solar_collector_area) / (cte.WATTS_HOUR_TO_JULES * 1e6) + yearly_solar_incident_str = format(yearly_solar_incident, '.2f') + yearly_pv_output = building.onsite_electrical_production[cte.YEAR][0] / (cte.WATTS_HOUR_TO_JULES * 1e6) + yearly_pv_output_str = format(yearly_pv_output, '.2f') + + pv_data.append(format(building.roofs[0].installed_solar_collector_area, '.2f')) + pv_data.append(yearly_solar_incident_str) + pv_data.append(yearly_pv_output_str) + + pv_output_table.append(pv_data) + + self.report.add_table(pv_output_table, caption='PV System Simulation Results', first_column_width=3) + + def life_cycle_cost_stacked_bar(self, file_name, title): + # Aggregate LCC components for current and retrofitted statuses + current_status_capex = 0 + current_status_opex = 0 + current_status_maintenance = 0 + current_status_end_of_life = 0 + retrofitted_capex = 0 + retrofitted_opex = 0 + retrofitted_maintenance = 0 + retrofitted_end_of_life = 0 + current_status_operational_income = 0 + retrofitted_operational_income = 0 + + for building in self.city.buildings: + current_status_capex += self.current_status_lcc[f'{building.name}']['capital_cost_per_sqm'] + retrofitted_capex += self.retrofitted_lcc[f'{building.name}']['capital_cost_per_sqm'] + current_status_opex += self.current_status_lcc[f'{building.name}']['operational_cost_per_sqm'] + retrofitted_opex += self.retrofitted_lcc[f'{building.name}']['operational_cost_per_sqm'] + current_status_maintenance += self.current_status_lcc[f'{building.name}']['maintenance_cost_per_sqm'] + retrofitted_maintenance += self.retrofitted_lcc[f'{building.name}']['maintenance_cost_per_sqm'] + current_status_end_of_life += self.current_status_lcc[f'{building.name}']['end_of_life_cost_per_sqm'] + retrofitted_end_of_life += self.retrofitted_lcc[f'{building.name}']['end_of_life_cost_per_sqm'] + current_status_operational_income += self.current_status_lcc[f'{building.name}']['operational_income_per_sqm'] + retrofitted_operational_income += self.retrofitted_lcc[f'{building.name}']['operational_income_per_sqm'] + + current_status_lcc_components_sqm = { + 'Capital Cost': current_status_capex / len(self.city.buildings), + 'Operational Cost': (current_status_opex - current_status_operational_income) / len(self.city.buildings), + 'Maintenance Cost': current_status_maintenance / len(self.city.buildings), + 'End of Life Cost': current_status_end_of_life / len(self.city.buildings), + } + retrofitted_lcc_components_sqm = { + 'Capital Cost': retrofitted_capex / len(self.city.buildings), + 'Operational Cost': (retrofitted_opex - retrofitted_operational_income) / len(self.city.buildings), + 'Maintenance Cost': retrofitted_maintenance / len(self.city.buildings), + 'End of Life Cost': retrofitted_end_of_life / len(self.city.buildings), + } + + labels = ['Current Status', 'Retrofitted Status'] + categories = ['Capital Cost', 'Operational Cost', 'Maintenance Cost', 'End of Life Cost'] + colors = ['#2196f3', '#ff5a5f', '#4caf50', '#ffc107'] # Added new color + + # Data preparation + bar_width = 0.35 + r = np.arange(len(labels)) + + fig, ax = plt.subplots(figsize=(12, 8), dpi=96) + fig.suptitle(title, fontsize=16, weight='bold', alpha=.8) + + # Plotting current status data + bottom = np.zeros(len(labels)) + for category, color in zip(categories, colors): + values = [current_status_lcc_components_sqm[category], retrofitted_lcc_components_sqm[category]] + ax.bar(r, values, bottom=bottom, color=color, edgecolor='white', width=bar_width, label=category) + bottom += values + + # Adding summation annotations at the top of the bars + for idx, (x, total) in enumerate(zip(r, bottom)): + ax.text(x, total, f'{total:.1f}', ha='center', va='bottom', fontsize=12, fontweight='bold') + + # Adding labels, title, and grid + ax.set_xlabel('LCC Components', fontsize=12, labelpad=10) + ax.set_ylabel('Average Cost (CAD/m²)', fontsize=14, labelpad=10) + ax.grid(which="major", axis='y', color='#DAD8D7', alpha=0.5, zorder=1) + ax.set_xticks(r) + ax.set_xticklabels(labels, rotation=45, ha='right') + ax.legend() + + # Adding a white background + fig.patch.set_facecolor('white') + + # Adjusting the margins around the plot area + plt.subplots_adjust(left=0.05, right=0.95, top=0.9, bottom=0.2) + + # Save the plot + chart_path = self.charts_path / f'{file_name}.png' + plt.savefig(chart_path, bbox_inches='tight') + plt.close() + + return chart_path + + def create_report(self): + # Add sections and text to the report + self.report.add_section('Overview of the Current Status in Buildings') + self.report.add_text('In this section, an overview of the current status of buildings characteristics, ' + 'energy demand and consumptions are provided') + self.report.add_subsection('Buildings Characteristics') + + self.building_energy_info() + + # current monthly demands and consumptions + current_monthly_demands = self.current_status_data['monthly_demands'] + current_monthly_consumptions = self.current_status_data['monthly_consumptions'] + + # Plot and save demand chart + current_demand_chart_path = self.plot_monthly_energy_demands(data=current_monthly_demands, + file_name='current_monthly_demands', + title='Current Status Monthly Energy Demands') + # Plot and save consumption chart + current_consumption_chart_path = self.plot_monthly_energy_consumption(data=current_monthly_consumptions, + file_name='monthly_consumptions', + title='Monthly Energy Consumptions') + current_consumption_breakdown_path = self.fuel_consumption_breakdown('City_Energy_Consumption_Breakdown', + self.current_status_data) + retrofitted_consumption_breakdown_path = self.fuel_consumption_breakdown( + 'fuel_consumption_breakdown_after_retrofit', + self.retrofitted_data) + life_cycle_cost_sqm_stacked_bar_chart_path = self.life_cycle_cost_stacked_bar('lcc_per_sqm', + 'LCC Analysis') + # Add current state of energy demands in the city + self.report.add_subsection('Current State of Energy Demands in the City') + self.report.add_text('The total monthly energy demands in the city are shown in Figure 1. It should be noted ' + 'that the electricity demand refers to total lighting and appliance electricity demands') + self.report.add_image(str(current_demand_chart_path).replace('\\', '/'), + 'Total Monthly Energy Demands in City', + placement='h') + + # Add current state of energy consumption in the city + self.report.add_subsection('Current State of Energy Consumption in the City') + self.report.add_text('The following figure shows the amount of energy consumed to supply heating, cooling, and ' + 'domestic hot water needs in the city. The details of the systems in each building before ' + 'and after retrofit are provided in Section 4. \par') + self.report.add_image(str(current_consumption_chart_path).replace('\\', '/'), + 'Total Monthly Energy Consumptions in City', + placement='H') + self.report.add_text('Figure 3 shows the yearly energy supplied to the city by fuel in different sectors. ' + 'All the values are in kWh.') + self.report.add_image(str(current_consumption_breakdown_path).replace('\\', '/'), + 'Current Energy Consumption Breakdown in the City by Fuel', + placement='H') + self.report.add_section(f'{self.retrofit_scenario}') + self.report.add_subsection('Proposed Systems') + self.energy_system_archetype_schematic() + if 'PV' in self.retrofit_scenario: + self.report.add_subsection('Rooftop Photovoltaic System Implementation') + self.pv_system() + if 'System' in self.retrofit_scenario: + self.report.add_subsection('Retrofitted HVAC and DHW Systems') + self.report.add_text('Figure 6 shows a comparison between total monthly energy consumption in the selected ' + 'buildings before and after retrofitting.') + consumption_comparison = self.energy_consumption_comparison(self.current_status_data['monthly_consumptions'], + self.retrofitted_data['monthly_consumptions'], + 'energy_consumption_comparison_in_city', + 'Total Monthly Energy Consumption Comparison in ' + 'Buildings') + self.report.add_image(str(consumption_comparison).replace('\\', '/'), + caption='Comparison of Total Monthly Energy Consumption in City Buildings', + placement='H') + self.yearly_consumption_comparison() + self.report.add_text('Figure 7 shows the fuel consumption breakdown in the area after the retrofit.') + self.report.add_image(str(retrofitted_consumption_breakdown_path).replace('\\', '/'), + caption=f'Fuel Consumption Breakdown After {self.retrofit_scenario}', + placement='H') + self.report.add_subsection('Life Cycle Cost Analysis') + self.report.add_image(str(life_cycle_cost_sqm_stacked_bar_chart_path).replace('\\', '/'), + caption='Average Life Cycle Cost Components', + placement='H') + + # Save and compile the report + self.report.save_report() + self.report.compile_to_pdf() diff --git a/scripts/energy_system_retrofit_results.py b/scripts/energy_system_retrofit_results.py index 034b9a2e..1b84601a 100644 --- a/scripts/energy_system_retrofit_results.py +++ b/scripts/energy_system_retrofit_results.py @@ -1,68 +1,176 @@ import hub.helpers.constants as cte -def system_results(buildings): - system_performance_summary = {} - fields = ["Energy System Archetype", "Heating Equipments", "Cooling Equipments", "DHW Equipments", - "Photovoltaic System Capacity", "Heating Fuel", "Yearly HVAC Energy Consumption (MWh)", - "DHW Energy Consumption (MWH)", "PV Yearly Production (kWh)", "LCC Analysis Duration (Years)", - "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 +def hourly_electricity_consumption_profile(building): + hourly_electricity_consumption = [] + energy_systems = building.energy_systems + appliance = building.appliances_electrical_demand[cte.HOUR] + lighting = building.lighting_electrical_demand[cte.HOUR] + elec_heating = 0 + elec_cooling = 0 + elec_dhw = 0 + if cte.HEATING in building.energy_consumption_breakdown[cte.ELECTRICITY]: + elec_heating = 1 + if cte.COOLING in building.energy_consumption_breakdown[cte.ELECTRICITY]: + elec_cooling = 1 + if cte.DOMESTIC_HOT_WATER in building.energy_consumption_breakdown[cte.ELECTRICITY]: + elec_dhw = 1 + heating = None + cooling = None + dhw = None + if elec_heating == 1: for energy_system in energy_systems: - demand_types = energy_system.demand_types - for demand_type in demand_types: - if demand_type == cte.COOLING: - equipments = [] - for generation_system in energy_system.generation_systems: - if generation_system.fuel_type == cte.ELECTRICITY: - equipments.append(generation_system.name or generation_system.system_type) - cooling_equipments = ", ".join(equipments) - system_performance_summary[f'{building.name}']['Cooling Equipments'] = cooling_equipments - elif demand_type == cte.HEATING: - equipments = [] - for generation_system in energy_system.generation_systems: - if generation_system.nominal_heat_output is not None: - equipments.append(generation_system.name or generation_system.system_type) - fuels.append(generation_system.fuel_type) - heating_equipments = ", ".join(equipments) - system_performance_summary[f'{building.name}']['Heating Equipments'] = heating_equipments - elif demand_type == cte.DOMESTIC_HOT_WATER: - equipments = [] - for generation_system in energy_system.generation_systems: - equipments.append(generation_system.name or generation_system.system_type) - dhw_equipments = ", ".join(equipments) - system_performance_summary[f'{building.name}']['DHW Equipments'] = dhw_equipments - 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 + if cte.HEATING in energy_system.demand_types: + for generation_system in energy_system.generation_systems: + if generation_system.fuel_type == cte.ELECTRICITY: + if cte.HEATING in generation_system.energy_consumption: + heating = generation_system.energy_consumption[cte.HEATING][cte.HOUR] + else: + if len(energy_system.generation_systems) > 1: + heating = [x / 2 for x in building.heating_consumption[cte.HOUR]] + else: + heating = building.heating_consumption[cte.HOUR] + if elec_dhw == 1: + for energy_system in energy_systems: + if cte.DOMESTIC_HOT_WATER in energy_system.demand_types: + for generation_system in energy_system.generation_systems: + if generation_system.fuel_type == cte.ELECTRICITY: + if cte.DOMESTIC_HOT_WATER in generation_system.energy_consumption: + dhw = generation_system.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.HOUR] + else: + if len(energy_system.generation_systems) > 1: + dhw = [x / 2 for x in building.domestic_hot_water_consumption[cte.HOUR]] + else: + dhw = building.domestic_hot_water_consumption[cte.HOUR] + + if elec_cooling == 1: + for energy_system in energy_systems: + if cte.COOLING in energy_system.demand_types: + for generation_system in energy_system.generation_systems: + if cte.COOLING in generation_system.energy_consumption: + cooling = generation_system.energy_consumption[cte.COOLING][cte.HOUR] + else: + if len(energy_system.generation_systems) > 1: + cooling = [x / 2 for x in building.cooling_consumption[cte.HOUR]] + else: + cooling = building.cooling_consumption[cte.HOUR] + + for i in range(len(building.heating_demand[cte.HOUR])): + hourly = 0 + hourly += appliance[i] / 3600 + hourly += lighting[i] / 3600 + if heating is not None: + hourly += heating[i] / 3600 + if cooling is not None: + hourly += cooling[i] / 3600 + if dhw is not None: + hourly += dhw[i] / 3600 + hourly_electricity_consumption.append(hourly) + return hourly_electricity_consumption -def new_system_results(buildings): - new_system_performance_summary = {} - fields = ["Energy System Archetype", "Heating Equipments", "Cooling Equipments", "DHW Equipments", - "Photovoltaic System Capacity", "Heating Fuel", "Yearly HVAC Energy Consumption (MWh)", - "DHW Energy Consumption (MWH)", "PV Yearly Production (kWh)", "LCC Analysis Duration (Years)", - "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 +def consumption_data(city): + energy_consumption_data = {} + for building in city.buildings: + hourly_electricity_consumption = hourly_electricity_consumption_profile(building) + energy_consumption_data[f'{building.name}'] = {'heating_consumption': building.heating_consumption, + 'cooling_consumption': building.cooling_consumption, + 'domestic_hot_water_consumption': + building.domestic_hot_water_consumption, + 'energy_consumption_breakdown': + building.energy_consumption_breakdown, + 'hourly_electricity_consumption': hourly_electricity_consumption} + peak_electricity_consumption = 0 + for building in energy_consumption_data: + peak_electricity_consumption += max(energy_consumption_data[building]['hourly_electricity_consumption']) + heating_demand_monthly = [] + cooling_demand_monthly = [] + dhw_demand_monthly = [] + lighting_appliance_monthly = [] + heating_consumption_monthly = [] + cooling_consumption_monthly = [] + dhw_consumption_monthly = [] + for i in range(12): + heating_demand = 0 + cooling_demand = 0 + dhw_demand = 0 + lighting_appliance_demand = 0 + heating_consumption = 0 + cooling_consumption = 0 + dhw_consumption = 0 + for building in city.buildings: + heating_demand += building.heating_demand[cte.MONTH][i] / 3.6e6 + cooling_demand += building.cooling_demand[cte.MONTH][i] / 3.6e6 + dhw_demand += building.domestic_hot_water_heat_demand[cte.MONTH][i] / 3.6e6 + lighting_appliance_demand += building.lighting_electrical_demand[cte.MONTH][i] / 3.6e6 + heating_consumption += building.heating_consumption[cte.MONTH][i] / 3.6e6 + if building.cooling_consumption[cte.YEAR][0] == 0: + cooling_consumption += building.cooling_demand[cte.MONTH][i] / (3.6e6 * 2) + else: + cooling_consumption += building.cooling_consumption[cte.MONTH][i] / 3.6e6 + dhw_consumption += building.domestic_hot_water_consumption[cte.MONTH][i] / 3.6e6 + heating_demand_monthly.append(heating_demand) + cooling_demand_monthly.append(cooling_demand) + dhw_demand_monthly.append(dhw_demand) + lighting_appliance_monthly.append(lighting_appliance_demand) + heating_consumption_monthly.append(heating_consumption) + cooling_consumption_monthly.append(cooling_consumption) + dhw_consumption_monthly.append(dhw_consumption) + + monthly_demands = {'heating': heating_demand_monthly, + 'cooling': cooling_demand_monthly, + 'dhw': dhw_demand_monthly, + 'lighting_appliance': lighting_appliance_monthly} + monthly_consumptions = {'heating': heating_consumption_monthly, + 'cooling': cooling_consumption_monthly, + 'dhw': dhw_consumption_monthly} + yearly_heating = 0 + yearly_cooling = 0 + yearly_dhw = 0 + yearly_appliance = 0 + yearly_lighting = 0 + for building in city.buildings: + yearly_appliance += building.appliances_electrical_demand[cte.YEAR][0] / 3.6e9 + yearly_lighting += building.lighting_electrical_demand[cte.YEAR][0] / 3.6e9 + yearly_heating += building.heating_consumption[cte.YEAR][0] / 3.6e9 + yearly_cooling += building.cooling_consumption[cte.YEAR][0] / 3.6e9 + yearly_dhw += building.domestic_hot_water_consumption[cte.YEAR][0] / 3.6e9 + + total_consumption = yearly_heating + yearly_cooling + yearly_dhw + yearly_appliance + yearly_lighting + energy_consumption_data['monthly_demands'] = monthly_demands + energy_consumption_data['monthly_consumptions'] = monthly_consumptions + energy_consumption_data['total_consumption'] = total_consumption + energy_consumption_data['maximum_hourly_electricity_consumption'] = peak_electricity_consumption + + return energy_consumption_data + + +def cost_data(building, lcc_dataframe, cost_retrofit_scenario): + total_floor_area = 0 + for thermal_zone in building.thermal_zones_from_internal_zones: + total_floor_area += thermal_zone.total_floor_area + capital_cost = lcc_dataframe.loc['total_capital_costs_systems', f'Scenario {cost_retrofit_scenario}'] + operational_cost = lcc_dataframe.loc['total_operational_costs', f'Scenario {cost_retrofit_scenario}'] + maintenance_cost = lcc_dataframe.loc['total_maintenance_costs', f'Scenario {cost_retrofit_scenario}'] + end_of_life_cost = lcc_dataframe.loc['end_of_life_costs', f'Scenario {cost_retrofit_scenario}'] + operational_income = lcc_dataframe.loc['operational_incomes', f'Scenario {cost_retrofit_scenario}'] + total_life_cycle_cost = capital_cost + operational_cost + maintenance_cost + end_of_life_cost + operational_income + specific_capital_cost = capital_cost / total_floor_area + specific_operational_cost = operational_cost / total_floor_area + specific_maintenance_cost = maintenance_cost / total_floor_area + specific_end_of_life_cost = end_of_life_cost / total_floor_area + specific_operational_income = operational_income / total_floor_area + specific_life_cycle_cost = total_life_cycle_cost / total_floor_area + life_cycle_cost_analysis = {'capital_cost': capital_cost, + 'capital_cost_per_sqm': specific_capital_cost, + 'operational_cost': operational_cost, + 'operational_cost_per_sqm': specific_operational_cost, + 'maintenance_cost': maintenance_cost, + 'maintenance_cost_per_sqm': specific_maintenance_cost, + 'end_of_life_cost': end_of_life_cost, + 'end_of_life_cost_per_sqm': specific_end_of_life_cost, + 'operational_income': operational_income, + 'operational_income_per_sqm': specific_operational_income, + 'total_life_cycle_cost': total_life_cycle_cost, + 'total_life_cycle_cost_per_sqm': specific_life_cycle_cost} + return life_cycle_cost_analysis diff --git a/scripts/energy_system_sizing.py b/scripts/energy_system_sizing.py index e626bc7d..77f876b4 100644 --- a/scripts/energy_system_sizing.py +++ b/scripts/energy_system_sizing.py @@ -52,17 +52,17 @@ class SystemSizing: 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] / 3600 + generation.nominal_heat_output = building.heating_peak_load[cte.YEAR][0] else: for generation in generation_systems: - generation.nominal_heat_output = building.heating_peak_load[cte.YEAR][0] / (len(generation_systems) * 3600) + generation.nominal_heat_output = building.heating_peak_load[cte.YEAR][0] / (len(generation_systems)) 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] / 3600 + generation.nominal_cooling_output = building.cooling_peak_load[cte.YEAR][0] else: for generation in generation_systems: - generation.nominal_heat_output = building.cooling_peak_load[cte.YEAR][0] / (len(generation_systems) * 3600) + generation.nominal_heat_output = building.cooling_peak_load[cte.YEAR][0] / (len(generation_systems)) diff --git a/scripts/energy_system_sizing_and_simulation_factory.py b/scripts/energy_system_sizing_and_simulation_factory.py index 95a4d4e8..9a0e14bb 100644 --- a/scripts/energy_system_sizing_and_simulation_factory.py +++ b/scripts/energy_system_sizing_and_simulation_factory.py @@ -6,7 +6,9 @@ Project Coder Saeed Ranjbar saeed.ranjbar@mail.concordia.ca """ from scripts.system_simulation_models.archetype13 import Archetype13 +from scripts.system_simulation_models.archetype13_stratified_tes import Archetype13Stratified from scripts.system_simulation_models.archetype1 import Archetype1 +from scripts.system_simulation_models.archetypes14_15 import Archetype14_15 class EnergySystemsSimulationFactory: @@ -35,6 +37,15 @@ class EnergySystemsSimulationFactory: self._building.level_of_detail.energy_systems = 2 self._building.level_of_detail.energy_systems = 2 + def _archetype14_15(self): + """ + Enrich the city by using the sizing and simulation model developed for archetype14 and archetype15 of + montreal_future_systems + """ + Archetype14_15(self._building, self._output_path).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 diff --git a/scripts/ep_run_enrich.py b/scripts/ep_run_enrich.py index 24ee4b11..68c24c8c 100644 --- a/scripts/ep_run_enrich.py +++ b/scripts/ep_run_enrich.py @@ -9,10 +9,10 @@ from hub.imports.results_factory import ResultFactory sys.path.append('./') -def energy_plus_workflow(city): +def energy_plus_workflow(city, output_path): try: # city = city - out_path = (Path(__file__).parent.parent / 'out_files') + out_path = output_path files = glob.glob(f'{out_path}/*') # for file in files: diff --git a/scripts/geojson_creator.py b/scripts/geojson_creator.py index f3c28b2e..c96c340d 100644 --- a/scripts/geojson_creator.py +++ b/scripts/geojson_creator.py @@ -4,13 +4,16 @@ from shapely import Point from pathlib import Path -def process_geojson(x, y, diff): +def process_geojson(x, y, diff, expansion=False): selection_box = Polygon([[x + diff, y - diff], [x - diff, y - diff], [x - diff, y + diff], [x + diff, y + diff]]) geojson_file = Path('./data/collinear_clean 2.geojson').resolve() - output_file = Path('./input_files/output_buildings.geojson').resolve() + if not expansion: + output_file = Path('./input_files/output_buildings.geojson').resolve() + else: + output_file = Path('./input_files/output_buildings_expanded.geojson').resolve() buildings_in_region = [] with open(geojson_file, 'r') as file: diff --git a/scripts/pv_feasibility.py b/scripts/pv_feasibility.py new file mode 100644 index 00000000..034a5efb --- /dev/null +++ b/scripts/pv_feasibility.py @@ -0,0 +1,37 @@ +from pathlib import Path +import subprocess +from hub.imports.geometry_factory import GeometryFactory +from scripts.geojson_creator import process_geojson +from hub.helpers.dictionaries import Dictionaries +from hub.imports.weather_factory import WeatherFactory +from hub.imports.results_factory import ResultFactory +from hub.exports.exports_factory import ExportsFactory + + +def pv_feasibility(current_x, current_y, current_diff, selected_buildings): + input_files_path = (Path(__file__).parent.parent / 'input_files') + output_path = (Path(__file__).parent.parent / 'out_files').resolve() + sra_output_path = output_path / 'sra_outputs' / 'extended_city_sra_outputs' + sra_output_path.mkdir(parents=True, exist_ok=True) + new_diff = current_diff * 5 + geojson_file = process_geojson(x=current_x, y=current_y, diff=new_diff, expansion=True) + file_path = input_files_path / 'output_buildings.geojson' + city = GeometryFactory('geojson', + path=file_path, + height_field='height', + year_of_construction_field='year_of_construction', + function_field='function', + function_to_hub=Dictionaries().montreal_function_to_hub_function).city + WeatherFactory('epw', city).enrich() + ExportsFactory('sra', city, sra_output_path).export() + sra_path = (sra_output_path / f'{city.name}_sra.xml').resolve() + subprocess.run(['sra', str(sra_path)]) + ResultFactory('sra', city, sra_output_path).enrich() + for selected_building in selected_buildings: + for building in city.buildings: + if selected_building.name == building.name: + selected_building.roofs[0].global_irradiance = building.roofs[0].global_irradiance + + + + diff --git a/scripts/pv_sizing_and_simulation.py b/scripts/pv_sizing_and_simulation.py new file mode 100644 index 00000000..6ef1a51d --- /dev/null +++ b/scripts/pv_sizing_and_simulation.py @@ -0,0 +1,59 @@ +import math + +from scripts.radiation_tilted import RadiationTilted +import hub.helpers.constants as cte +from hub.helpers.monthly_values import MonthlyValues + + +class PVSizingSimulation(RadiationTilted): + def __init__(self, building, solar_angles, tilt_angle, module_height, module_width, ghi): + super().__init__(building, solar_angles, tilt_angle, ghi) + self.module_height = module_height + self.module_width = module_width + self.total_number_of_panels = 0 + self.enrich() + + def available_space(self): + roof_area = self.building.roofs[0].perimeter_area + maintenance_factor = 0.1 + orientation_factor = 0.2 + if self.building.function == cte.RESIDENTIAL: + mechanical_equipment_factor = 0.2 + else: + mechanical_equipment_factor = 0.3 + available_roof = (maintenance_factor + orientation_factor + mechanical_equipment_factor) * roof_area + return available_roof + + def inter_row_spacing(self): + winter_solstice = self.df[(self.df['AST'].dt.month == 12) & + (self.df['AST'].dt.day == 21) & + (self.df['AST'].dt.hour == 12)] + solar_altitude = winter_solstice['solar altitude'].values[0] + solar_azimuth = winter_solstice['solar azimuth'].values[0] + distance = ((self.module_height * abs(math.cos(math.radians(solar_azimuth)))) / + math.tan(math.radians(solar_altitude))) + distance = float(format(distance, '.1f')) + return distance + + def number_of_panels(self, available_roof, inter_row_distance): + space_dimension = math.sqrt(available_roof) + space_dimension = float(format(space_dimension, '.2f')) + panels_per_row = math.ceil(space_dimension / self.module_width) + number_of_rows = math.ceil(space_dimension / inter_row_distance) + self.total_number_of_panels = panels_per_row * number_of_rows + return panels_per_row, number_of_rows + + def pv_output(self): + radiation = self.total_radiation_tilted + pv_module_area = self.module_width * self.module_height + available_roof = self.available_space() + inter_row_spacing = self.inter_row_spacing() + self.number_of_panels(available_roof, inter_row_spacing) + self.building.roofs[0].installed_solar_collector_area = pv_module_area * self.total_number_of_panels + system_efficiency = 0.2 + pv_hourly_production = [x * system_efficiency * self.total_number_of_panels * pv_module_area * + cte.WATTS_HOUR_TO_JULES for x in radiation] + self.building.onsite_electrical_production[cte.HOUR] = pv_hourly_production + self.building.onsite_electrical_production[cte.MONTH] = ( + MonthlyValues.get_total_month(self.building.onsite_electrical_production[cte.HOUR])) + self.building.onsite_electrical_production[cte.YEAR] = [sum(self.building.onsite_electrical_production[cte.MONTH])] \ No newline at end of file diff --git a/scripts/radiation_tilted.py b/scripts/radiation_tilted.py new file mode 100644 index 00000000..beb62088 --- /dev/null +++ b/scripts/radiation_tilted.py @@ -0,0 +1,113 @@ +import pandas as pd +import math +import hub.helpers.constants as cte +from hub.helpers.monthly_values import MonthlyValues + + +class RadiationTilted: + def __init__(self, building, solar_angles, tilt_angle, ghi, solar_constant=1366.1, maximum_clearness_index=1, + min_cos_zenith=0.065, maximum_zenith_angle=87): + self.building = building + self.ghi = ghi + self.tilt_angle = tilt_angle + self.zeniths = solar_angles['zenith'].tolist()[:-1] + self.incidents = solar_angles['incident angle'].tolist()[:-1] + self.date_time = solar_angles['DateTime'].tolist()[:-1] + self.ast = solar_angles['AST'].tolist()[:-1] + self.solar_azimuth = solar_angles['solar azimuth'].tolist()[:-1] + self.solar_altitude = solar_angles['solar altitude'].tolist()[:-1] + data = {'DateTime': self.date_time, 'AST': self.ast, 'solar altitude': self.solar_altitude, 'zenith': self.zeniths, + 'solar azimuth': self.solar_azimuth, 'incident angle': self.incidents, 'ghi': self.ghi} + self.df = pd.DataFrame(data) + self.df['DateTime'] = pd.to_datetime(self.df['DateTime']) + self.df['AST'] = pd.to_datetime(self.df['AST']) + self.df.set_index('DateTime', inplace=True) + self.solar_constant = solar_constant + self.maximum_clearness_index = maximum_clearness_index + self.min_cos_zenith = min_cos_zenith + self.maximum_zenith_angle = maximum_zenith_angle + self.i_on = [] + self.i_oh = [] + self.k_t = [] + self.fraction_diffuse = [] + self.diffuse_horizontal = [] + self.beam_horizontal = [] + self.dni = [] + self.beam_tilted = [] + self.diffuse_tilted = [] + self.total_radiation_tilted = [] + self.calculate() + + def dni_extra(self): + for i in range(len(self.df)): + self.i_on.append(self.solar_constant * (1 + 0.033 * math.cos(math.radians(360 * self.df.index.dayofyear[i] / 365)))) + + self.df['extraterrestrial normal radiation (Wh/m2)'] = self.i_on + + def clearness_index(self): + for i in range(len(self.df)): + self.i_oh.append(self.i_on[i] * max(math.cos(math.radians(self.zeniths[i])), self.min_cos_zenith)) + self.k_t.append(self.ghi[i] / self.i_oh[i]) + self.k_t[i] = max(0, self.k_t[i]) + self.k_t[i] = min(self.maximum_clearness_index, self.k_t[i]) + self.df['extraterrestrial radiation on horizontal (Wh/m2)'] = self.i_oh + self.df['clearness index'] = self.k_t + + def diffuse_fraction(self): + for i in range(len(self.df)): + if self.k_t[i] <= 0.22: + self.fraction_diffuse.append(1 - 0.09 * self.k_t[i]) + elif self.k_t[i] <= 0.8: + self.fraction_diffuse.append(0.9511 - 0.1604 * self.k_t[i] + 4.388 * self.k_t[i] ** 2 - + 16.638 * self.k_t[i] ** 3 + 12.336 * self.k_t[i] ** 4) + else: + self.fraction_diffuse.append(0.165) + if self.zeniths[i] > self.maximum_zenith_angle: + self.fraction_diffuse[i] = 1 + + self.df['diffuse fraction'] = self.fraction_diffuse + + def radiation_components_horizontal(self): + for i in range(len(self.df)): + self.diffuse_horizontal.append(self.ghi[i] * self.fraction_diffuse[i]) + self.beam_horizontal.append(self.ghi[i] - self.diffuse_horizontal[i]) + self.dni.append((self.ghi[i] - self.diffuse_horizontal[i]) / math.cos(math.radians(self.zeniths[i]))) + if self.zeniths[i] > self.maximum_zenith_angle or self.dni[i] < 0: + self.dni[i] = 0 + + self.df['diffuse horizontal (Wh/m2)'] = self.diffuse_horizontal + self.df['dni (Wh/m2)'] = self.dni + self.df['beam horizontal (Wh/m2)'] = self.beam_horizontal + + def radiation_components_tilted(self): + for i in range(len(self.df)): + self.beam_tilted.append(self.dni[i] * math.cos(math.radians(self.incidents[i]))) + self.beam_tilted[i] = max(self.beam_tilted[i], 0) + self.diffuse_tilted.append(self.diffuse_horizontal[i] * ((1 + math.cos(math.radians(self.tilt_angle))) / 2)) + self.total_radiation_tilted.append(self.beam_tilted[i] + self.diffuse_tilted[i]) + + self.df['beam tilted (Wh/m2)'] = self.beam_tilted + self.df['diffuse tilted (Wh/m2)'] = self.diffuse_tilted + self.df['total radiation tilted (Wh/m2)'] = self.total_radiation_tilted + + def calculate(self) -> pd.DataFrame: + self.dni_extra() + self.clearness_index() + self.diffuse_fraction() + self.radiation_components_horizontal() + self.radiation_components_tilted() + return self.df + + def enrich(self): + tilted_radiation = self.total_radiation_tilted + self.building.roofs[0].global_irradiance_tilted[cte.HOUR] = [x * cte.WATTS_HOUR_TO_JULES for x in + tilted_radiation] + self.building.roofs[0].global_irradiance_tilted[cte.HOUR] = [x * cte.WATTS_HOUR_TO_JULES for x in + tilted_radiation] + self.building.roofs[0].global_irradiance_tilted[cte.MONTH] = ( + MonthlyValues.get_total_month(self.building.roofs[0].global_irradiance_tilted[cte.HOUR])) + self.building.roofs[0].global_irradiance_tilted[cte.YEAR] = \ + [sum(self.building.roofs[0].global_irradiance_tilted[cte.MONTH])] + + + diff --git a/scripts/report_creation.py b/scripts/report_creation.py index cca587f4..6298d232 100644 --- a/scripts/report_creation.py +++ b/scripts/report_creation.py @@ -1,73 +1,119 @@ import subprocess import datetime +import os +from pathlib import Path + class LatexReport: - def __init__(self, file_name): - self.file_name = file_name - self.content = [] - self.content.append(r'\documentclass{article}') - self.content.append(r'\usepackage[margin=2.5cm]{geometry}') # Adjust page margins - self.content.append(r'\usepackage{graphicx}') - self.content.append(r'\usepackage{tabularx}') - self.content.append(r'\begin{document}') - # Get current date and time - current_datetime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") - self.content.append(r'\title{Energy System Analysis Report - ' + current_datetime + r'}') - self.content.append(r'\author{Next-Generation Cities Institute}') - self.content.append(r'\date{}') # Remove the date field, as it's included in the title now - self.content.append(r'\maketitle') + def __init__(self, file_name, title, subtitle, output_path): + self.file_name = file_name + self.output_path = Path(output_path) / 'report' + self.output_path.mkdir(parents=True, exist_ok=True) + self.file_path = self.output_path / f"{file_name}.tex" + self.content = [] + self.content.append(r'\documentclass{article}') + self.content.append(r'\usepackage[margin=2.5cm]{geometry}') + self.content.append(r'\usepackage{graphicx}') + self.content.append(r'\usepackage{tabularx}') + self.content.append(r'\usepackage{multirow}') + self.content.append(r'\usepackage{float}') + self.content.append(r'\begin{document}') + + current_datetime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + self.content.append(r'\title{' + title + '}') + self.content.append(r'\author{Next-Generation Cities Institute}') + self.content.append(r'\date{}') + self.content.append(r'\maketitle') + + self.content.append(r'\begin{center}') + self.content.append(r'\large ' + subtitle + r'\\') + self.content.append(r'\large ' + current_datetime) + self.content.append(r'\end{center}') def add_section(self, section_title): - self.content.append(r'\section{' + section_title + r'}') + self.content.append(r'\section{' + section_title + r'}') def add_subsection(self, subsection_title): - self.content.append(r'\subsection{' + subsection_title + r'}') + self.content.append(r'\subsection{' + subsection_title + r'}') + + def add_subsubsection(self, subsection_title): + self.content.append(r'\subsubsection{' + subsection_title + r'}') def add_text(self, text): - self.content.append(text) + self.content.append(text) - def add_table(self, table_data, caption=None, first_column_width=None): + def add_table(self, table_data, caption=None, first_column_width=None, merge_first_column=False): num_columns = len(table_data[0]) - total_width = 0.9 # Default total width + total_width = 0.9 + first_column_width_str = '' if first_column_width is not None: first_column_width_str = str(first_column_width) + 'cm' - total_width -= first_column_width / 16.0 # Adjust total width for the first column + total_width -= first_column_width / 16.0 if caption: self.content.append(r'\begin{table}[htbp]') self.content.append(r'\caption{' + caption + r'}') self.content.append(r'\centering') - self.content.append(r'\begin{tabularx}{\textwidth}{|p{' + first_column_width_str + r'}|' + '|'.join(['X'] * ( - num_columns - 1)) + '|}' if first_column_width is not None else r'\begin{tabularx}{\textwidth}{|' + '|'.join( - ['X'] * num_columns) + '|}') + column_format = '|p{' + first_column_width_str + r'}|' + '|'.join( + ['X'] * (num_columns - 1)) + '|' if first_column_width is not None else '|' + '|'.join(['X'] * num_columns) + '|' + self.content.append(r'\begin{tabularx}{\textwidth}{' + column_format + '}') self.content.append(r'\hline') - for row in table_data: - self.content.append(' & '.join(row) + r' \\') + + previous_first_column = None + rowspan_count = 1 + + for i, row in enumerate(table_data): + if merge_first_column and i > 0 and row[0] == previous_first_column: + rowspan_count += 1 + self.content.append(' & '.join(['' if j == 0 else cell for j, cell in enumerate(row)]) + r' \\') + else: + if merge_first_column and i > 0 and rowspan_count > 1: + self.content[-rowspan_count] = self.content[-rowspan_count].replace(r'\multirow{1}', + r'\multirow{' + str(rowspan_count) + '}') + rowspan_count = 1 + if merge_first_column and i < len(table_data) - 1 and row[0] == table_data[i + 1][0]: + self.content.append(r'\multirow{1}{*}{' + row[0] + '}' + ' & ' + ' & '.join(row[1:]) + r' \\') + else: + self.content.append(' & '.join(row) + r' \\') + previous_first_column = row[0] self.content.append(r'\hline') + + if merge_first_column and rowspan_count > 1: + self.content[-rowspan_count] = self.content[-rowspan_count].replace(r'\multirow{1}', + r'\multirow{' + str(rowspan_count) + '}') + self.content.append(r'\end{tabularx}') if caption: self.content.append(r'\end{table}') - def add_image(self, image_path, caption=None): + def add_image(self, image_path, caption=None, placement='ht'): if caption: - self.content.append(r'\begin{figure}[htbp]') + self.content.append(r'\begin{figure}[' + placement + r']') self.content.append(r'\centering') - self.content.append(r'\includegraphics[width=0.8\textwidth]{' + image_path + r'}') + self.content.append(r'\includegraphics[width=\textwidth]{' + image_path + r'}') self.content.append(r'\caption{' + caption + r'}') self.content.append(r'\end{figure}') else: - self.content.append(r'\begin{figure}[htbp]') + self.content.append(r'\begin{figure}[' + placement + r']') self.content.append(r'\centering') - self.content.append(r'\includegraphics[width=0.8\textwidth]{' + image_path + r'}') + self.content.append(r'\includegraphics[width=\textwidth]{' + image_path + r'}') self.content.append(r'\end{figure}') + def add_itemize(self, items): + self.content.append(r'\begin{itemize}') + for item in items: + self.content.append(r'\item ' + item) + self.content.append(r'\end{itemize}') + def save_report(self): - self.content.append(r'\end{document}') # Add this line to close the document - with open(self.file_name, 'w') as f: + self.content.append(r'\end{document}') + with open(self.file_path, 'w') as f: f.write('\n'.join(self.content)) def compile_to_pdf(self): - subprocess.run(['pdflatex', self.file_name]) + subprocess.run(['pdflatex', '-output-directory', str(self.output_path), str(self.file_path)]) + diff --git a/scripts/solar_angles.py b/scripts/solar_angles.py new file mode 100644 index 00000000..d65fcf58 --- /dev/null +++ b/scripts/solar_angles.py @@ -0,0 +1,146 @@ +import math +import pandas as pd +from datetime import datetime +from pathlib import Path + + +class CitySolarAngles: + def __init__(self, file_name, location_latitude, location_longitude, tilt_angle, surface_azimuth_angle, + standard_meridian=-75): + self.file_name = file_name + self.location_latitude = location_latitude + self.location_longitude = location_longitude + self.location_latitude_rad = math.radians(location_latitude) + self.surface_azimuth_angle = surface_azimuth_angle + self.surface_azimuth_rad = math.radians(surface_azimuth_angle) + self.tilt_angle = tilt_angle + self.tilt_angle_rad = math.radians(tilt_angle) + self.standard_meridian = standard_meridian + self.longitude_correction = (location_longitude - standard_meridian) * 4 + self.timezone = 'Etc/GMT+5' + + self.eot = [] + self.ast = [] + self.hour_angles = [] + self.declinations = [] + self.solar_altitudes = [] + self.solar_azimuths = [] + self.zeniths = [] + self.incidents = [] + self.beam_tilted = [] + self.factor = [] + self.times = pd.date_range(start='2023-01-01', end='2024-01-01', freq='H', tz=self.timezone) + self.df = pd.DataFrame(index=self.times) + self.day_of_year = self.df.index.dayofyear + + def solar_time(self, datetime_val, day_of_year): + b = (day_of_year - 81) * 2 * math.pi / 364 + eot = 9.87 * math.sin(2 * b) - 7.53 * math.cos(b) - 1.5 * math.sin(b) + self.eot.append(eot) + + # Calculate Local Solar Time (LST) + lst_hour = datetime_val.hour + lst_minute = datetime_val.minute + lst_second = datetime_val.second + lst = lst_hour + lst_minute / 60 + lst_second / 3600 + + # Calculate Apparent Solar Time (AST) in decimal hours + ast_decimal = lst + eot / 60 + self.longitude_correction / 60 + ast_hours = int(ast_decimal) + ast_minutes = round((ast_decimal - ast_hours) * 60) + + # Ensure ast_minutes is within valid range + if ast_minutes == 60: + ast_hours += 1 + ast_minutes = 0 + elif ast_minutes < 0: + ast_minutes = 0 + ast_time = datetime(year=datetime_val.year, month=datetime_val.month, day=datetime_val.day, + hour=ast_hours, minute=ast_minutes) + self.ast.append(ast_time) + return ast_time + + def declination_angle(self, day_of_year): + declination = 23.45 * math.sin(math.radians(360 / 365 * (284 + day_of_year))) + declination_radian = math.radians(declination) + self.declinations.append(declination) + return declination_radian + + def hour_angle(self, ast_time): + hour_angle = ((ast_time.hour * 60 + ast_time.minute) - 720) / 4 + hour_angle_radian = math.radians(hour_angle) + self.hour_angles.append(hour_angle) + return hour_angle_radian + + def solar_altitude(self, declination_radian, hour_angle_radian): + solar_altitude_radians = math.asin(math.cos(self.location_latitude_rad) * math.cos(declination_radian) * + math.cos(hour_angle_radian) + math.sin(self.location_latitude_rad) * + math.sin(declination_radian)) + solar_altitude = math.degrees(solar_altitude_radians) + self.solar_altitudes.append(solar_altitude) + return solar_altitude_radians + + def zenith(self, solar_altitude_radians): + solar_altitude = math.degrees(solar_altitude_radians) + zenith_degree = 90 - solar_altitude + zenith_radian = math.radians(zenith_degree) + self.zeniths.append(zenith_degree) + return zenith_radian + + def solar_azimuth_analytical(self, hourangle, declination, zenith): + numer = (math.cos(zenith) * math.sin(self.location_latitude_rad) - math.sin(declination)) + denom = (math.sin(zenith) * math.cos(self.location_latitude_rad)) + if math.isclose(denom, 0.0, abs_tol=1e-8): + cos_azi = 1.0 + else: + cos_azi = numer / denom + + cos_azi = max(-1.0, min(1.0, cos_azi)) + + sign_ha = math.copysign(1, hourangle) + solar_azimuth_radians = sign_ha * math.acos(cos_azi) + math.pi + solar_azimuth_degrees = math.degrees(solar_azimuth_radians) + self.solar_azimuths.append(solar_azimuth_degrees) + return solar_azimuth_radians + + def incident_angle(self, solar_altitude_radians, solar_azimuth_radians): + incident_radian = math.acos(math.cos(solar_altitude_radians) * + math.cos(abs(solar_azimuth_radians - self.surface_azimuth_rad)) * + math.sin(self.tilt_angle_rad) + math.sin(solar_altitude_radians) * + math.cos(self.tilt_angle_rad)) + incident_angle_degrees = math.degrees(incident_radian) + self.incidents.append(incident_angle_degrees) + return incident_radian + + @property + def calculate(self) -> pd.DataFrame: + for i in range(len(self.times)): + datetime_val = self.times[i] + day_of_year = self.day_of_year[i] + declination_radians = self.declination_angle(day_of_year) + ast_time = self.solar_time(datetime_val, day_of_year) + hour_angle_radians = self.hour_angle(ast_time) + solar_altitude_radians = self.solar_altitude(declination_radians, hour_angle_radians) + zenith_radians = self.zenith(solar_altitude_radians) + solar_azimuth_radians = self.solar_azimuth_analytical(hour_angle_radians, declination_radians, zenith_radians) + incident_angle_radian = self.incident_angle(solar_altitude_radians, solar_azimuth_radians) + + self.df['DateTime'] = self.times + self.df['AST'] = self.ast + self.df['hour angle'] = self.hour_angles + self.df['eot'] = self.eot + self.df['declination angle'] = self.declinations + self.df['solar altitude'] = self.solar_altitudes + self.df['zenith'] = self.zeniths + self.df['solar azimuth'] = self.solar_azimuths + self.df['incident angle'] = self.incidents + + return self.df + + + + + + + + diff --git a/scripts/system_simulation.py b/scripts/system_simulation.py deleted file mode 100644 index a097d0b0..00000000 --- a/scripts/system_simulation.py +++ /dev/null @@ -1,135 +0,0 @@ -import csv -import math -from typing import List - -from pathlib import Path - -import hub.helpers.constants as cte -from hub.helpers.monthly_values import MonthlyValues - - -class SystemSimulation: - def __init__(self, building, out_path): - self.building = building - self.energy_systems = building.energy_systems - 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] - self.maximum_heating_demand = building.heating_peak_load[cte.YEAR][0] - 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 = 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) - hp_electricity: List[float] = [0.0] * len(self.heating_demand) - aux_fuel: List[float] = [0.0] * len(self.heating_demand) - heating_consumption: List[float] = [0.0] * len(self.heating_demand) - boiler_consumption: List[float] = [0.0] * len(self.heating_demand) - T[0], dt = 25, 3600 # Assuming dt is defined somewhere - ua, v, hp_cap, hp_efficiency, boiler_efficiency = 0, 0, 0, 0, 0 - for energy_system in self.energy_systems: - 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 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: - if storage.type_energy_stored == 'thermal': - v, h = float(storage.volume), float(storage.height) - r_tot = sum(float(layer.thickness) / float(layer.material.conductivity) for layer in - storage.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) - elif generation_system.system_type == cte.BOILER: - boiler_cap = generation_system.nominal_heat_output - boiler_efficiency = float(generation_system.heat_efficiency) - - for i in range(len(self.heating_demand) - 1): - T[i + 1] = T[i] + ((m_ch[i] * (T_sup[i] - T[i])) + ( - ua * (self.T_out[i] - T[i])) / cte.WATER_HEAT_CAPACITY - m_dis[i] * (T[i] - T_ret[i])) * (dt / (cte.WATER_DENSITY * v)) - if T[i + 1] < 35: - q_hp[i + 1] = hp_cap * 1000 - 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[i + 1] - elif 35 <= T[i + 1] < 45 and q_hp[i] == 0: - q_hp[i + 1] = 0 - m_ch[i + 1] = 0 - T_sup[i + 1] = T[i + 1] - elif 35 <= T[i + 1] < 45 and q_hp[i] > 0: - q_hp[i + 1] = hp_cap * 1000 - 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[i + 1] - else: - q_hp[i + 1], m_ch[i + 1], T_sup[i + 1] = 0, 0, T[i + 1] - - hp_electricity[i + 1] = q_hp[i + 1] / hp_efficiency - if self.heating_demand[i + 1] == 0: - m_dis[i + 1], t_return, T_ret[i + 1] = 0, T[i + 1], T[i + 1] - else: - if self.heating_demand[i + 1] > 0.5 * self.maximum_heating_demand: - factor = 8 - else: - factor = 4 - 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 * 3600) - 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 * 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] / 3600): - q_aux[i + 1] = (self.heating_demand[i + 1] / 3600) - tes_output - aux_fuel[i + 1] = (q_aux[i + 1] * dt) / 35.8e6 - boiler_consumption[i + 1] = q_aux[i + 1] / boiler_efficiency - heating_consumption[i + 1] = boiler_consumption[i + 1] + hp_electricity[i + 1] - data = list(zip(T, T_sup, T_ret, m_ch, m_dis, q_hp, hp_electricity, aux_fuel, q_aux, self.heating_demand)) - file_name = f'simulation_results_{self.name}.csv' - with open(out_path / file_name, 'w', newline='') as csvfile: - output_file = csv.writer(csvfile) - # Write header - 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, T_sup - - def enrich(self): - 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]) - self.building.heating_consumption[cte.YEAR] = [sum(self.building.heating_consumption[cte.MONTH])] - disaggregated_consumption = {} - 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][ - cte.HOUR] = building_heating_electricity_consumption - disaggregated_consumption[generation_system.fuel_type][cte.MONTH] = MonthlyValues.get_total_month( - disaggregated_consumption[generation_system.fuel_type][cte.HOUR]) - disaggregated_consumption[generation_system.fuel_type][cte.YEAR] = [ - sum(disaggregated_consumption[generation_system.fuel_type][cte.MONTH])] - else: - disaggregated_consumption[generation_system.fuel_type][cte.HOUR] = building_heating_gas_consumption - disaggregated_consumption[generation_system.fuel_type][cte.MONTH] = MonthlyValues.get_total_month( - disaggregated_consumption[generation_system.fuel_type][cte.HOUR]) - disaggregated_consumption[generation_system.fuel_type][cte.YEAR] = [ - sum(disaggregated_consumption[generation_system.fuel_type][cte.MONTH])] - self.building.heating_fuel_consumption_disaggregated = disaggregated_consumption - return self.building - - diff --git a/scripts/system_simulation_models/archetype1.py b/scripts/system_simulation_models/archetype1.py index 19af7eed..6e8bc4f1 100644 --- a/scripts/system_simulation_models/archetype1.py +++ b/scripts/system_simulation_models/archetype1.py @@ -128,7 +128,7 @@ class Archetype1: hp = self.hvac_sizing()[0] eer_curve_coefficients = [float(coefficient) for coefficient in hp.cooling_efficiency_curve.coefficients] cooling_efficiency = float(hp.cooling_efficiency) - demand = self._hourly_heating_demand + demand = self._hourly_cooling_demand hp.source_temperature = self._t_out variable_names = ["t_sup_hp", "t_ret", "m", "q_hp", "hp_electricity", "hp_eer"] num_hours = len(demand) diff --git a/scripts/system_simulation_models/archetype13.py b/scripts/system_simulation_models/archetype13.py index 03057cdc..aa9c03bd 100644 --- a/scripts/system_simulation_models/archetype13.py +++ b/scripts/system_simulation_models/archetype13.py @@ -1,49 +1,73 @@ import math - import hub.helpers.constants as cte +import csv +from hub.helpers.monthly_values import MonthlyValues class Archetype13: def __init__(self, building, output_path): + self._building = building + self._name = building.name self._pv_system = building.energy_systems[0] self._hvac_system = building.energy_systems[1] self._dhw_system = building.energy_systems[-1] + self._dhw_peak_flow_rate = (building.thermal_zones_from_internal_zones[0].total_floor_area * + building.thermal_zones_from_internal_zones[0].domestic_hot_water.peak_flow * + cte.WATER_DENSITY) 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._hourly_heating_demand = [demand / cte.WATTS_HOUR_TO_JULES for demand in building.heating_demand[cte.HOUR]] + self._hourly_cooling_demand = [demand / cte.WATTS_HOUR_TO_JULES for demand in building.cooling_demand[cte.HOUR]] + self._hourly_dhw_demand = [demand / cte.WATTS_HOUR_TO_JULES for demand in + building.domestic_hot_water_heat_demand[cte.HOUR]] self._output_path = output_path - self._t_out = building.external_temperature + self._t_out = building.external_temperature[cte.HOUR] + self.results = {} + self.dt = 900 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) + heat_pump = self._hvac_system.generation_systems[1] + boiler = self._hvac_system.generation_systems[0] + thermal_storage = boiler.energy_storage_systems[0] + heat_pump.nominal_heat_output = round(0.5 * self._heating_peak_load) + heat_pump.nominal_cooling_output = round(self._cooling_peak_load) + boiler.nominal_heat_output = round(0.5 * self._heating_peak_load) thermal_storage.volume = round( - (self._heating_peak_load * storage_factor) / (cte.WATER_HEAT_CAPACITY * cte.WATER_DENSITY * 30)) + (self._heating_peak_load * storage_factor * cte.WATTS_HOUR_TO_JULES) / + (cte.WATER_HEAT_CAPACITY * cte.WATER_DENSITY * 25)) return heat_pump, boiler, thermal_storage - def hvac_simulation(self): + def dhw_sizing(self): + storage_factor = 3 + dhw_hp = self._dhw_system.generation_systems[0] + dhw_hp.nominal_heat_output = 0.7 * self._domestic_hot_water_peak_load + dhw_hp.source_temperature = self._t_out + dhw_tes = dhw_hp.energy_storage_systems[0] + dhw_tes.volume = round( + (self._domestic_hot_water_peak_load * storage_factor * 3600) / (cte.WATER_HEAT_CAPACITY * cte.WATER_DENSITY * 10)) + return dhw_hp, dhw_tes + + def heating_system_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) + cop_curve_coefficients = [float(coefficient) for coefficient in hp.heat_efficiency_curve.coefficients] + number_of_ts = int(cte.HOUR_TO_SECONDS / self.dt) + demand = [0] + [x for x in self._hourly_heating_demand for _ in range(number_of_ts)] + t_out = [0] + [x for x in self._t_out for _ in range(number_of_ts)] + hp.source_temperature = self._t_out + variable_names = ["t_sup_hp", "t_tank", "t_ret", "m_ch", "m_dis", "q_hp", "q_boiler", "hp_cop", + "hp_electricity", "boiler_gas_consumption", "t_sup_boiler", "boiler_energy_consumption", + "heating_consumption"] + num_hours = len(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 + (t_sup_hp, t_tank, t_ret, m_ch, m_dis, q_hp, q_boiler, hp_cop, + hp_electricity, boiler_gas_consumption, t_sup_boiler, boiler_energy_consumption, heating_consumption) = \ + [variables[name] for name in variable_names] + t_tank[0] = 55 hp_heating_cap = hp.nominal_heat_output - hp_efficiency = float(hp.heat_efficiency) + boiler_heating_cap = boiler.nominal_heat_output + hp_delta_t = 5 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 @@ -53,48 +77,312 @@ class Archetype13: 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): + # storage temperature prediction + for i in range(len(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))) + (m_ch[i] * (t_sup_boiler[i] - t_tank[i]) + + (ua * (t_out[i] - t_tank[i])) / cte.WATER_HEAT_CAPACITY - + m_dis[i] * (t_tank[i] - t_ret[i])) * (self.dt / (cte.WATER_DENSITY * v))) + # hp operation 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: + m_ch[i + 1] = q_hp[i + 1] / (cte.WATER_HEAT_CAPACITY * hp_delta_t) + t_sup_hp[i + 1] = (q_hp[i + 1] / (m_ch[i + 1] * cte.WATER_HEAT_CAPACITY)) + t_tank[i + 1] + elif 40 <= 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: + t_sup_hp[i + 1] = t_tank[i + 1] + elif 40 <= 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] + m_ch[i + 1] = q_hp[i + 1] / (cte.WATER_HEAT_CAPACITY * hp_delta_t) + t_sup_hp[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] + q_hp[i + 1], m_ch[i + 1], t_sup_hp[i + 1] = 0, 0, t_tank[i + 1] + t_sup_hp_fahrenheit = 1.8 * t_sup_hp[i + 1] + 32 + t_out_fahrenheit = 1.8 * t_out[i + 1] + 32 + if q_hp[i + 1] > 0: + hp_cop[i + 1] = (cop_curve_coefficients[0] + + cop_curve_coefficients[1] * t_sup_hp_fahrenheit + + cop_curve_coefficients[2] * t_sup_hp_fahrenheit ** 2 + + cop_curve_coefficients[3] * t_out_fahrenheit + + cop_curve_coefficients[4] * t_out_fahrenheit ** 2 + + cop_curve_coefficients[5] * t_sup_hp_fahrenheit * t_out_fahrenheit) + hp_electricity[i + 1] = q_hp[i + 1] / hp_cop[i + 1] else: - if self._hourly_heating_demand[i + 1] > 0.5 * self._heating_peak_load: + hp_cop[i + 1] = 0 + hp_electricity[i + 1] = 0 + # boiler operation + if q_hp[i + 1] > 0: + if t_sup_hp[i + 1] < 45: + q_boiler[i + 1] = boiler_heating_cap + elif demand[i + 1] > 0.5 * self._heating_peak_load / self.dt: + q_boiler[i + 1] = 0.5 * boiler_heating_cap + boiler_energy_consumption[i + 1] = q_boiler[i + 1] / boiler_efficiency + boiler_gas_consumption[i + 1] = (q_boiler[i + 1] * self.dt) / (boiler_efficiency * cte.NATURAL_GAS_LHV) + t_sup_boiler[i + 1] = t_sup_hp[i + 1] + (q_boiler[i + 1] / (m_ch[i + 1] * cte.WATER_HEAT_CAPACITY)) + # storage discharging + if demand[i + 1] == 0: + m_dis[i + 1] = 0 + t_ret[i + 1] = t_tank[i + 1] + else: + if 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]) + m_dis[i + 1] = self._heating_peak_load / (cte.WATER_HEAT_CAPACITY * factor) + t_ret[i + 1] = t_tank[i + 1] - demand[i + 1] / (m_dis[i + 1] * cte.WATER_HEAT_CAPACITY) + tes.temperature = [] + hp_electricity_j = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in hp_electricity] + boiler_consumption_j = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in boiler_energy_consumption] + hp_hourly = [] + boiler_hourly = [] + boiler_sum = 0 + hp_sum = 0 + for i in range(1, len(demand)): + hp_sum += hp_electricity_j[i] + boiler_sum += boiler_consumption_j[i] + if (i - 1) % number_of_ts == 0: + tes.temperature.append(t_tank[i]) + hp_hourly.append(hp_sum) + boiler_hourly.append(boiler_sum) + hp_sum = 0 + boiler_sum = 0 + hp.energy_consumption[cte.HEATING] = {} + hp.energy_consumption[cte.HEATING][cte.HOUR] = hp_hourly + hp.energy_consumption[cte.HEATING][cte.MONTH] = MonthlyValues.get_total_month( + hp.energy_consumption[cte.HEATING][cte.HOUR]) + hp.energy_consumption[cte.HEATING][cte.YEAR] = [ + sum(hp.energy_consumption[cte.HEATING][cte.MONTH])] + boiler.energy_consumption[cte.HEATING] = {} + boiler.energy_consumption[cte.HEATING][cte.HOUR] = boiler_hourly + boiler.energy_consumption[cte.HEATING][cte.MONTH] = MonthlyValues.get_total_month( + boiler.energy_consumption[cte.HEATING][cte.HOUR]) + boiler.energy_consumption[cte.HEATING][cte.YEAR] = [ + sum(boiler.energy_consumption[cte.HEATING][cte.MONTH])] + + self.results['Heating Demand (W)'] = demand + self.results['HP Heat Output (W)'] = q_hp + self.results['HP Source Temperature'] = t_out + self.results['HP Supply Temperature'] = t_sup_hp + self.results['HP COP'] = hp_cop + self.results['HP Electricity Consumption (W)'] = hp_electricity + self.results['Boiler Heat Output (W)'] = q_boiler + self.results['Boiler Supply Temperature'] = t_sup_boiler + self.results['Boiler Gas Consumption'] = boiler_gas_consumption + self.results['TES Temperature'] = t_tank + self.results['TES Charging Flow Rate (kg/s)'] = m_ch + self.results['TES Discharge Flow Rate (kg/s)'] = m_dis + self.results['Heating Loop Return Temperature'] = t_ret + return hp_hourly, boiler_hourly + + def cooling_system_simulation(self): + hp = self.hvac_sizing()[0] + eer_curve_coefficients = [float(coefficient) for coefficient in hp.cooling_efficiency_curve.coefficients] + cooling_efficiency = float(hp.cooling_efficiency) + number_of_ts = int(cte.HOUR_TO_SECONDS / self.dt) + demand = [0] + [x for x in self._hourly_cooling_demand for _ in range(number_of_ts)] + t_out = [0] + [x for x in self._t_out for _ in range(number_of_ts)] + hp.source_temperature = self._t_out + variable_names = ["t_sup_hp", "t_ret", "m", "q_hp", "hp_electricity", "hp_eer"] + num_hours = len(demand) + variables = {name: [0] * num_hours for name in variable_names} + (t_sup_hp, t_ret, m, q_hp, hp_electricity, hp_eer) = [variables[name] for name in variable_names] + t_ret[0] = 13 + + for i in range(1, len(demand)): + if demand[i] > 0.15 * self._cooling_peak_load: + m[i] = hp.nominal_cooling_output / (cte.WATER_HEAT_CAPACITY * 5) + if t_ret[i - 1] >= 13: + if demand[i] < 0.25 * self._cooling_peak_load: + q_hp[i] = 0.25 * hp.nominal_cooling_output + elif demand[i] < 0.5 * self._cooling_peak_load: + q_hp[i] = 0.5 * hp.nominal_cooling_output + else: + q_hp[i] = hp.nominal_cooling_output + t_sup_hp[i] = t_ret[i - 1] - q_hp[i] / (m[i] * cte.WATER_HEAT_CAPACITY) 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)) + q_hp[i] = 0 + t_sup_hp[i] = t_ret[i - 1] + if m[i] == 0: + t_ret[i] = t_sup_hp[i] + else: + t_ret[i] = t_sup_hp[i] + demand[i] / (m[i] * cte.WATER_HEAT_CAPACITY) + else: + m[i] = 0 + q_hp[i] = 0 + t_sup_hp[i] = t_ret[i - 1] + t_ret[i] = t_ret[i - 1] + t_sup_hp_fahrenheit = 1.8 * t_sup_hp[i] + 32 + t_out_fahrenheit = 1.8 * t_out[i] + 32 + if q_hp[i] > 0: + hp_eer[i] = (eer_curve_coefficients[0] + + eer_curve_coefficients[1] * t_sup_hp_fahrenheit + + eer_curve_coefficients[2] * t_sup_hp_fahrenheit ** 2 + + eer_curve_coefficients[3] * t_out_fahrenheit + + eer_curve_coefficients[4] * t_out_fahrenheit ** 2 + + eer_curve_coefficients[5] * t_sup_hp_fahrenheit * t_out_fahrenheit) + hp_electricity[i] = q_hp[i] / cooling_efficiency + else: + hp_eer[i] = 0 + hp_electricity[i] = 0 + hp_electricity_j = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in hp_electricity] + hp_hourly = [] + hp_sum = 0 + for i in range(1, len(demand)): + hp_sum += hp_electricity_j[i] + if (i - 1) % number_of_ts == 0: + hp_hourly.append(hp_sum) + hp_sum = 0 + hp.energy_consumption[cte.COOLING] = {} + hp.energy_consumption[cte.COOLING][cte.HOUR] = hp_hourly + hp.energy_consumption[cte.COOLING][cte.MONTH] = MonthlyValues.get_total_month( + hp.energy_consumption[cte.COOLING][cte.HOUR]) + hp.energy_consumption[cte.COOLING][cte.YEAR] = [ + sum(hp.energy_consumption[cte.COOLING][cte.MONTH])] + self.results['Cooling Demand (W)'] = demand + self.results['HP Cooling Output (W)'] = q_hp + self.results['HP Cooling Supply Temperature'] = t_sup_hp + self.results['HP Cooling COP'] = hp_eer + self.results['HP Electricity Consumption'] = hp_electricity + self.results['Cooling Loop Flow Rate (kg/s)'] = m + self.results['Cooling Loop Return Temperature'] = t_ret + return hp_hourly + + def dhw_system_simulation(self): + hp, tes = self.dhw_sizing() + cop_curve_coefficients = [float(coefficient) for coefficient in hp.heat_efficiency_curve.coefficients] + number_of_ts = int(cte.HOUR_TO_SECONDS / self.dt) + demand = [0] + [x for x in self._hourly_dhw_demand for _ in range(number_of_ts)] + t_out = [0] + [x for x in self._t_out for _ in range(number_of_ts)] + variable_names = ["t_sup_hp", "t_tank", "m_ch", "m_dis", "q_hp", "q_coil", "hp_cop", + "hp_electricity", "available hot water (m3)", "refill flow rate (kg/s)"] + num_hours = len(demand) + variables = {name: [0] * num_hours for name in variable_names} + (t_sup_hp, t_tank, m_ch, m_dis, m_refill, q_hp, q_coil, hp_cop, hp_electricity, v_dhw) = \ + [variables[name] for name in variable_names] + t_tank[0] = 70 + v_dhw[0] = tes.volume + + hp_heating_cap = hp.nominal_heat_output + hp_delta_t = 8 + 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) + freshwater_temperature = 18 + for i in range(len(demand) - 1): + delta_t_demand = demand[i] * (self.dt / (cte.WATER_DENSITY * cte.WATER_HEAT_CAPACITY * v)) + if t_tank[i] < 65: + q_hp[i] = hp_heating_cap + delta_t_hp = q_hp[i] * (self.dt / (cte.WATER_DENSITY * cte.WATER_HEAT_CAPACITY * v)) + if demand[i] > 0: + dhw_needed = (demand[i] * cte.HOUR_TO_SECONDS) / (cte.WATER_HEAT_CAPACITY * t_tank[i] * cte.WATER_DENSITY) + m_dis[i] = dhw_needed * cte.WATER_DENSITY / cte.HOUR_TO_SECONDS + m_refill[i] = m_dis[i] + delta_t_freshwater = m_refill[i] * (t_tank[i] - freshwater_temperature) * (self.dt / (v * cte.WATER_DENSITY)) + diff = delta_t_freshwater + delta_t_demand - delta_t_hp + if diff > 0: + if diff > 0: + power = diff * (cte.WATER_DENSITY * cte.WATER_HEAT_CAPACITY * v) / self.dt + if power <= float(tes.heating_coil_capacity): + q_coil[i] = power + else: + q_coil[i] = float(tes.heating_coil_capacity) + delta_t_coil = q_coil[i] * (self.dt / (cte.WATER_DENSITY * cte.WATER_HEAT_CAPACITY * v)) + + if q_hp[i] > 0: + m_ch[i] = q_hp[i] / (cte.WATER_HEAT_CAPACITY * hp_delta_t) + t_sup_hp[i] = (q_hp[i] / (m_ch[i] * cte.WATER_HEAT_CAPACITY)) + t_tank[i] + else: + m_ch[i] = 0 + t_sup_hp[i] = t_tank[i] + t_sup_hp_fahrenheit = 1.8 * t_sup_hp[i] + 32 + t_out_fahrenheit = 1.8 * t_out[i] + 32 + if q_hp[i] > 0: + hp_cop[i] = (cop_curve_coefficients[0] + + cop_curve_coefficients[1] * t_sup_hp_fahrenheit + + cop_curve_coefficients[2] * t_sup_hp_fahrenheit ** 2 + + cop_curve_coefficients[3] * t_out_fahrenheit + + cop_curve_coefficients[4] * t_out_fahrenheit ** 2 + + cop_curve_coefficients[5] * t_sup_hp_fahrenheit * t_out_fahrenheit) + hp_electricity[i] = q_hp[i] / hp_cop[i] + else: + hp_cop[i] = 0 + hp_electricity[i] = 0 + + t_tank[i + 1] = t_tank[i] + (delta_t_hp - delta_t_freshwater - delta_t_demand + delta_t_coil) + tes.temperature = [] + hp_electricity_j = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in hp_electricity] + heating_coil_j = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in q_coil] + hp_hourly = [] + coil_hourly = [] + coil_sum = 0 + hp_sum = 0 + for i in range(1, len(demand)): + hp_sum += hp_electricity_j[i] + coil_sum += heating_coil_j[i] + if (i - 1) % number_of_ts == 0: + tes.temperature.append(t_tank[i]) + hp_hourly.append(hp_sum) + coil_hourly.append(coil_sum) + hp_sum = 0 + coil_sum = 0 + + hp.energy_consumption[cte.DOMESTIC_HOT_WATER] = {} + hp.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.HOUR] = hp_hourly + hp.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.MONTH] = MonthlyValues.get_total_month( + hp.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.HOUR]) + hp.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.YEAR] = [ + sum(hp.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.MONTH])] + tes.heating_coil_energy_consumption = {} + tes.heating_coil_energy_consumption[cte.HOUR] = coil_hourly + tes.heating_coil_energy_consumption[cte.MONTH] = MonthlyValues.get_total_month( + tes.heating_coil_energy_consumption[cte.HOUR]) + tes.heating_coil_energy_consumption[cte.YEAR] = [ + sum(tes.heating_coil_energy_consumption[cte.MONTH])] + tes.temperature = t_tank + + self.results['DHW Demand (W)'] = demand + self.results['DHW HP Heat Output (W)'] = q_hp + self.results['DHW HP Electricity Consumption (W)'] = hp_electricity + self.results['DHW HP Source Temperature'] = t_out + self.results['DHW HP Supply Temperature'] = t_sup_hp + self.results['DHW HP COP'] = hp_cop + self.results['DHW TES Heating Coil Heat Output (W)'] = q_coil + self.results['DHW TES Temperature'] = t_tank + self.results['DHW TES Charging Flow Rate (kg/s)'] = m_ch + self.results['DHW Flow Rate (kg/s)'] = m_dis + self.results['DHW TES Refill Flow Rate (kg/s)'] = m_refill + self.results['Available Water in Tank (m3)'] = v_dhw + return hp_hourly, coil_hourly def enrich_buildings(self): - self.hvac_sizing() + hp_heating, boiler_consumption = self.heating_system_simulation() + hp_cooling = self.cooling_system_simulation() + hp_dhw, heating_coil = self.dhw_system_simulation() + heating_consumption = [hp_heating[i] + boiler_consumption[i] for i in range(len(hp_heating))] + dhw_consumption = [hp_dhw[i] + heating_coil[i] for i in range(len(hp_dhw))] + self._building.heating_consumption[cte.HOUR] = heating_consumption + self._building.heating_consumption[cte.MONTH] = ( + MonthlyValues.get_total_month(self._building.heating_consumption[cte.HOUR])) + self._building.heating_consumption[cte.YEAR] = [sum(self._building.heating_consumption[cte.MONTH])] + self._building.cooling_consumption[cte.HOUR] = hp_cooling + self._building.cooling_consumption[cte.MONTH] = ( + MonthlyValues.get_total_month(self._building.cooling_consumption[cte.HOUR])) + self._building.cooling_consumption[cte.YEAR] = [sum(self._building.cooling_consumption[cte.MONTH])] + self._building.domestic_hot_water_consumption[cte.HOUR] = dhw_consumption + self._building.domestic_hot_water_consumption[cte.MONTH] = ( + MonthlyValues.get_total_month(self._building.domestic_hot_water_consumption[cte.HOUR])) + self._building.domestic_hot_water_consumption[cte.YEAR] = [ + sum(self._building.domestic_hot_water_consumption[cte.MONTH])] + file_name = f'energy_system_simulation_results_{self._name}.csv' + with open(self._output_path / file_name, 'w', newline='') as csvfile: + output_file = csv.writer(csvfile) + # Write header + output_file.writerow(self.results.keys()) + # Write data + output_file.writerows(zip(*self.results.values())) diff --git a/scripts/system_simulation_models/archetype13_stratified_tes.py b/scripts/system_simulation_models/archetype13_stratified_tes.py new file mode 100644 index 00000000..c5bdd1e7 --- /dev/null +++ b/scripts/system_simulation_models/archetype13_stratified_tes.py @@ -0,0 +1,392 @@ +import math +import hub.helpers.constants as cte +import csv +from hub.helpers.monthly_values import MonthlyValues +import numpy as np + + +class Archetype13Stratified: + def __init__(self, building, output_path): + self._building = building + self._name = building.name + self._pv_system = building.energy_systems[0] + self._hvac_system = building.energy_systems[1] + self._dhw_system = building.energy_systems[-1] + self._dhw_peak_flow_rate = (building.thermal_zones_from_internal_zones[0].total_floor_area * + building.thermal_zones_from_internal_zones[0].domestic_hot_water.peak_flow * + cte.WATER_DENSITY) + 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 = [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 = [0] + building.domestic_hot_water_heat_demand[cte.HOUR] + self._output_path = output_path + self._t_out = building.external_temperature[cte.HOUR] + self.results = {} + + def hvac_sizing(self): + storage_factor = 3 + heat_pump = self._hvac_system.generation_systems[1] + boiler = self._hvac_system.generation_systems[0] + thermal_storage = boiler.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 * 25)) + return heat_pump, boiler, thermal_storage + + def dhw_sizing(self): + storage_factor = 3 + dhw_hp = self._dhw_system.generation_systems[0] + dhw_hp.nominal_heat_output = 0.7 * self._domestic_hot_water_peak_load + dhw_hp.source_temperature = self._t_out + dhw_tes = dhw_hp.energy_storage_systems[0] + dhw_tes.volume = round( + (self._domestic_hot_water_peak_load * storage_factor * 3600) / (cte.WATER_HEAT_CAPACITY * cte.WATER_DENSITY * 10)) + return dhw_hp, dhw_tes + + def heating_system_simulation_stratified(self): + hp, boiler, tes = self.hvac_sizing() + cop_curve_coefficients = [float(coefficient) for coefficient in hp.heat_efficiency_curve.coefficients] + demand = [0] + [x for x in self._hourly_heating_demand for _ in range(12)] + hp.source_temperature = self._t_out + t_out = [0] + [x for x in self._t_out for _ in range(12)] + variable_names = ["t_sup_hp", "t1", "t2", "t3", "t4", "t_tank", "t_ret", "m_ch", "m_dis", "q_hp", "q_boiler", + "hp_cop", "hp_electricity", "boiler_gas_consumption", "t_sup_boiler", "boiler_energy_consumption", + "heating_consumption"] + num_hours = len(demand) + variables = {name: [0] * num_hours for name in variable_names} + (t_sup_hp, t1, t2, t3, t4, t_tank, t_ret, m_ch, m_dis, q_hp, q_boiler, hp_cop, + hp_electricity, boiler_gas_consumption, t_sup_boiler, boiler_energy_consumption, heating_consumption) = \ + [variables[name] for name in variable_names] + t_tank[0] = 55 + t1[0] = 55 + t2[0] = 55 + t3[0] = 55 + t4[0] = 55 + dt = 300 + hp_heating_cap = hp.nominal_heat_output + boiler_heating_cap = boiler.nominal_heat_output + hp_delta_t = 5 + boiler_efficiency = float(boiler.heat_efficiency) + v, h = float(tes.volume) / 4, float(tes.height) / 4 + 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_side = u_tot * a_side + ua_top_bottom = u_tot * (a_top + a_side) + # storage temperature prediction + for i in range(len(demand) - 1): + t1[i + 1] = t1[i] + ((m_ch[i] * (t_sup_boiler[i] - t1[i])) + ( + np.heaviside((m_dis[i] - m_ch[i]), 0) * (m_ch[i] - m_dis[i]) * (t1[i] - t2[i])) + ( + ua_top_bottom * (t_out[i] - t1[i])) / cte.WATER_HEAT_CAPACITY - cte.WATER_THERMAL_CONDUCTIVITY * (a_top * (t1[i] - t2[i])) / ( + cte.WATER_HEAT_CAPACITY * h)) * (dt / (cte.WATER_DENSITY * v)) + t2[i + 1] = t2[i] + ((np.heaviside((m_dis[i] - m_ch[i]), 0) * (m_ch[i] - m_dis[i]) * (t2[i] - t3[i])) + ( + ua_side * (t_out[i] - t2[i])) / cte.WATER_HEAT_CAPACITY - (cte.WATER_THERMAL_CONDUCTIVITY * (a_top * (t2[i] - t1[i])) / (cte.WATER_HEAT_CAPACITY * h)) - ( + cte.WATER_THERMAL_CONDUCTIVITY * (a_top * (t2[i] - t3[i])) / (cte.WATER_HEAT_CAPACITY * h)) + ( + np.heaviside((m_ch[i] - m_dis[i]), 0) * (m_ch[i] - m_dis[i]) * ( + t1[i] - t2[i]))) * (dt / (cte.WATER_DENSITY * v)) + t3[i + 1] = t3[i] + ((np.heaviside((m_dis[i] - m_ch[i]), 0) * (m_ch[i] - m_dis[i]) * (t3[i] - t4[i])) + ( + ua_side * (t_out[i] - t3[i])) / cte.WATER_HEAT_CAPACITY - (cte.WATER_THERMAL_CONDUCTIVITY * (a_top * (t3[i] - t2[i])) / (cte.WATER_HEAT_CAPACITY * h)) - ( + cte.WATER_THERMAL_CONDUCTIVITY * (a_top * (t3[i] - t4[i])) / (cte.WATER_HEAT_CAPACITY * h)) + ( + np.heaviside((m_ch[i] - m_dis[i]), 0) * (m_ch[i] - m_dis[i]) * ( + t2[i] - t3[i]))) * (dt / (cte.WATER_DENSITY * v)) + t4[i + 1] = t4[i] + (np.heaviside((m_ch[i] - m_dis[i]), 0) * ((m_ch[i] - m_dis[i]) * (t3[i] - t4[i])) + ( + ua_top_bottom * (t_out[i] - t4[-1])) / cte.WATER_HEAT_CAPACITY - m_dis[i] * ((t4[i] - t_ret[i])) - ( + cte.WATER_THERMAL_CONDUCTIVITY * (a_top * (t4[i] - t3[i])) / (cte.WATER_HEAT_CAPACITY * h))) * (dt / (cte.WATER_DENSITY * v)) + # hp operation + if t1[i + 1] < 40: + q_hp[i + 1] = hp_heating_cap + m_ch[i + 1] = q_hp[i + 1] / (cte.WATER_HEAT_CAPACITY * hp_delta_t) + t_sup_hp[i + 1] = (q_hp[i + 1] / (m_ch[i + 1] * cte.WATER_HEAT_CAPACITY)) + t4[i + 1] + elif 40 <= t1[i + 1] < 55 and q_hp[i] == 0: + q_hp[i + 1] = 0 + m_ch[i + 1] = 0 + t_sup_hp[i + 1] = t4[i + 1] + elif 40 <= t1[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 * hp_delta_t) + t_sup_hp[i + 1] = (q_hp[i + 1] / (m_ch[i + 1] * cte.WATER_HEAT_CAPACITY)) + t4[i + 1] + else: + q_hp[i + 1], m_ch[i + 1], t_sup_hp[i + 1] = 0, 0, t4[i + 1] + t_sup_hp_fahrenheit = 1.8 * t_sup_hp[i + 1] + 32 + t_out_fahrenheit = 1.8 * t_out[i + 1] + 32 + if q_hp[i + 1] > 0: + hp_cop[i + 1] = (cop_curve_coefficients[0] + + cop_curve_coefficients[1] * t_sup_hp_fahrenheit + + cop_curve_coefficients[2] * t_sup_hp_fahrenheit ** 2 + + cop_curve_coefficients[3] * t_out_fahrenheit + + cop_curve_coefficients[4] * t_out_fahrenheit ** 2 + + cop_curve_coefficients[5] * t_sup_hp_fahrenheit * t_out_fahrenheit) + hp_electricity[i + 1] = q_hp[i + 1] / hp_cop[i + 1] + else: + hp_cop[i + 1] = 0 + hp_electricity[i + 1] = 0 + # boiler operation + if q_hp[i + 1] > 0: + if t_sup_hp[i + 1] < 45: + q_boiler[i + 1] = boiler_heating_cap + elif demand[i + 1] > 0.5 * self._heating_peak_load / dt: + q_boiler[i + 1] = 0.5 * boiler_heating_cap + boiler_energy_consumption[i + 1] = q_boiler[i + 1] / boiler_efficiency + boiler_gas_consumption[i + 1] = (q_boiler[i + 1] * dt) / (boiler_efficiency * cte.NATURAL_GAS_LHV) + t_sup_boiler[i + 1] = t_sup_hp[i + 1] + (q_boiler[i + 1] / (m_ch[i + 1] * cte.WATER_HEAT_CAPACITY)) + # storage discharging + if demand[i + 1] == 0: + m_dis[i + 1] = 0 + t_ret[i + 1] = t1[i + 1] + else: + if demand[i + 1] > 0.5 * self._heating_peak_load / cte.HOUR_TO_SECONDS: + factor = 8 + else: + factor = 4 + m_dis[i + 1] = self._heating_peak_load / (cte.WATER_HEAT_CAPACITY * factor * cte.HOUR_TO_SECONDS) + t_ret[i + 1] = t1[i + 1] - demand[i + 1] / (m_dis[i + 1] * cte.WATER_HEAT_CAPACITY) + + hp_electricity_wh = [x / 12 for x in hp_electricity] + boiler_consumption_wh = [x / 12 for x in boiler_energy_consumption] + hp_hourly = [] + boiler_hourly = [] + tes.temperature = {} + tes.temperature['layer_1'] = [] + tes.temperature['layer_2'] = [] + tes.temperature['layer_3'] = [] + tes.temperature['layer_4'] = [] + for i in range(1, len(demand), 12): + tes.temperature['layer_1'].append(t1[i]) + tes.temperature['layer_2'].append(t2[i]) + tes.temperature['layer_3'].append(t3[i]) + tes.temperature['layer_4'].append(t4[i]) + demand_modified = demand[1:] + hp_hourly.append(hp_electricity[1]) + boiler_hourly.append(boiler_energy_consumption[1]) + boiler_sum = 0 + hp_sum = 0 + for i in range(1, len(demand_modified) + 1): + hp_sum += hp_electricity_wh[i] + boiler_sum += boiler_consumption_wh[i] + if i % 12 == 0: + hp_hourly.append(hp_sum) + boiler_hourly.append(boiler_sum) + hp_sum = 0 + boiler_sum = 0 + + hp.energy_consumption[cte.HEATING] = {} + hp.energy_consumption[cte.HEATING][cte.HOUR] = hp_hourly + hp.energy_consumption[cte.HEATING][cte.MONTH] = MonthlyValues.get_total_month( + hp.energy_consumption[cte.HEATING][cte.HOUR]) + hp.energy_consumption[cte.HEATING][cte.YEAR] = [ + sum(hp.energy_consumption[cte.HEATING][cte.MONTH])] + boiler.energy_consumption[cte.HEATING] = {} + boiler.energy_consumption[cte.HEATING][cte.HOUR] = boiler_hourly + boiler.energy_consumption[cte.HEATING][cte.MONTH] = MonthlyValues.get_total_month( + boiler.energy_consumption[cte.HEATING][cte.HOUR]) + boiler.energy_consumption[cte.HEATING][cte.YEAR] = [ + sum(boiler.energy_consumption[cte.HEATING][cte.MONTH])] + + self.results['Heating Demand (W)'] = demand + self.results['HP Heat Output (W)'] = q_hp + self.results['HP Source Temperature'] = t_out + self.results['HP Supply Temperature'] = t_sup_hp + self.results['HP COP'] = hp_cop + self.results['HP Electricity Consumption (W)'] = hp_electricity + self.results['Boiler Heat Output (W)'] = q_boiler + self.results['Boiler Supply Temperature'] = t_sup_boiler + self.results['Boiler Gas Consumption'] = boiler_gas_consumption + self.results['TES Layer 1 Temperature'] = t1 + self.results['TES Layer 2 Temperature'] = t2 + self.results['TES Layer 3 Temperature'] = t3 + self.results['TES Layer 4 Temperature'] = t4 + self.results['TES Charging Flow Rate (kg/s)'] = m_ch + self.results['TES Discharge Flow Rate (kg/s)'] = m_dis + self.results['Heating Loop Return Temperature'] = t_ret + return hp_electricity, boiler_energy_consumption + + def cooling_system_simulation(self): + hp = self.hvac_sizing()[0] + eer_curve_coefficients = [float(coefficient) for coefficient in hp.cooling_efficiency_curve.coefficients] + cooling_efficiency = float(hp.cooling_efficiency) + demand = self._hourly_cooling_demand + hp.source_temperature = self._t_out + variable_names = ["t_sup_hp", "t_ret", "m", "q_hp", "hp_electricity", "hp_eer"] + num_hours = len(demand) + variables = {name: [0] * num_hours for name in variable_names} + (t_sup_hp, t_ret, m, q_hp, hp_electricity, hp_eer) = [variables[name] for name in variable_names] + t_ret[0] = 13 + dt = 3600 + for i in range(len(demand) - 1): + if demand[i] > 0: + m[i] = self._cooling_peak_load / (cte.WATER_HEAT_CAPACITY * 5 * dt) + if t_ret[i] > 13: + if demand[i] < 0.25 * self._cooling_peak_load / dt: + q_hp[i] = 0.25 * hp.nominal_cooling_output + elif demand[i] < 0.5 * self._cooling_peak_load / dt: + q_hp[i] = 0.5 * hp.nominal_cooling_output + else: + q_hp[i] = hp.nominal_cooling_output + t_sup_hp[i] = t_ret[i] - q_hp[i] / (m[i] * cte.WATER_HEAT_CAPACITY) + else: + q_hp[i] = 0 + t_sup_hp[i] = t_ret[i] + t_ret[i + 1] = t_sup_hp[i] + demand[i] / (m[i] * cte.WATER_HEAT_CAPACITY) + else: + m[i] = 0 + q_hp[i] = 0 + t_sup_hp[i] = t_ret[i] + t_ret[i + 1] = t_ret[i] + t_sup_hp_fahrenheit = 1.8 * t_sup_hp[i] + 32 + t_out_fahrenheit = 1.8 * self._t_out[i] + 32 + if q_hp[i] > 0: + hp_eer[i] = (eer_curve_coefficients[0] + + eer_curve_coefficients[1] * t_sup_hp_fahrenheit + + eer_curve_coefficients[2] * t_sup_hp_fahrenheit ** 2 + + eer_curve_coefficients[3] * t_out_fahrenheit + + eer_curve_coefficients[4] * t_out_fahrenheit ** 2 + + eer_curve_coefficients[5] * t_sup_hp_fahrenheit * t_out_fahrenheit) + hp_electricity[i] = q_hp[i] / cooling_efficiency + else: + hp_eer[i] = 0 + hp_electricity[i] = 0 + hp.energy_consumption[cte.COOLING] = {} + hp.energy_consumption[cte.COOLING][cte.HOUR] = hp_electricity + hp.energy_consumption[cte.COOLING][cte.MONTH] = MonthlyValues.get_total_month( + hp.energy_consumption[cte.COOLING][cte.HOUR]) + hp.energy_consumption[cte.COOLING][cte.YEAR] = [ + sum(hp.energy_consumption[cte.COOLING][cte.MONTH])] + # self.results['Cooling Demand (W)'] = demand + # self.results['HP Cooling Output (W)'] = q_hp + # self.results['HP Cooling Supply Temperature'] = t_sup_hp + # self.results['HP Cooling COP'] = hp_eer + # self.results['HP Electricity Consumption'] = hp_electricity + # self.results['Cooling Loop Flow Rate (kg/s)'] = m + # self.results['Cooling Loop Return Temperature'] = t_ret + return hp_electricity + + def dhw_system_simulation(self): + hp, tes = self.dhw_sizing() + cop_curve_coefficients = [float(coefficient) for coefficient in hp.heat_efficiency_curve.coefficients] + demand = self._hourly_dhw_demand + variable_names = ["t_sup_hp", "t_tank", "m_ch", "m_dis", "q_hp", "q_coil", "hp_cop", + "hp_electricity", "available hot water (m3)", "refill flow rate (kg/s)"] + num_hours = len(demand) + variables = {name: [0] * num_hours for name in variable_names} + (t_sup_hp, t_tank, m_ch, m_dis, m_refill, q_hp, q_coil, hp_cop, hp_electricity, v_dhw) = \ + [variables[name] for name in variable_names] + t_tank[0] = 70 + v_dhw[0] = tes.volume + dt = 3600 + hp_heating_cap = hp.nominal_heat_output + hp_delta_t = 8 + 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) + freshwater_temperature = 18 + for i in range(len(demand) - 1): + delta_t_demand = demand[i] * (dt / (cte.WATER_DENSITY * cte.WATER_HEAT_CAPACITY * v)) + if t_tank[i] < 65: + q_hp[i] = hp_heating_cap + delta_t_hp = q_hp[i] * (dt / (cte.WATER_DENSITY * cte.WATER_HEAT_CAPACITY * v)) + if demand[i] > 0: + dhw_needed = (demand[i] * cte.HOUR_TO_SECONDS) / (cte.WATER_HEAT_CAPACITY * t_tank[i] * cte.WATER_DENSITY) + m_dis[i] = dhw_needed * cte.WATER_DENSITY / cte.HOUR_TO_SECONDS + m_refill[i] = m_dis[i] + delta_t_freshwater = m_refill[i] * (t_tank[i] - freshwater_temperature) * (dt / (v * cte.WATER_DENSITY)) + diff = delta_t_freshwater + delta_t_demand - delta_t_hp + if diff > 0: + if diff > 0: + power = diff * (cte.WATER_DENSITY * cte.WATER_HEAT_CAPACITY * v) / dt + if power <= float(tes.heating_coil_capacity): + q_coil[i] = power + else: + q_coil[i] = float(tes.heating_coil_capacity) + elif t_tank[i] < 65: + q_coil[i] = float(tes.heating_coil_capacity) + delta_t_coil = q_coil[i] * (dt / (cte.WATER_DENSITY * cte.WATER_HEAT_CAPACITY * v)) + + if q_hp[i] > 0: + m_ch[i] = q_hp[i] / (cte.WATER_HEAT_CAPACITY * hp_delta_t) + t_sup_hp[i] = (q_hp[i] / (m_ch[i] * cte.WATER_HEAT_CAPACITY)) + t_tank[i] + else: + m_ch[i] = 0 + t_sup_hp[i] = t_tank[i] + t_sup_hp_fahrenheit = 1.8 * t_sup_hp[i] + 32 + t_out_fahrenheit = 1.8 * self._t_out[i] + 32 + if q_hp[i] > 0: + hp_cop[i] = (cop_curve_coefficients[0] + + cop_curve_coefficients[1] * t_sup_hp_fahrenheit + + cop_curve_coefficients[2] * t_sup_hp_fahrenheit ** 2 + + cop_curve_coefficients[3] * t_out_fahrenheit + + cop_curve_coefficients[4] * t_out_fahrenheit ** 2 + + cop_curve_coefficients[5] * t_sup_hp_fahrenheit * t_out_fahrenheit) + hp_electricity[i] = q_hp[i] / 3.5 + else: + hp_cop[i] = 0 + hp_electricity[i] = 0 + + t_tank[i + 1] = t_tank[i] + (delta_t_hp - delta_t_freshwater - delta_t_demand + delta_t_coil) + + hp.energy_consumption[cte.DOMESTIC_HOT_WATER] = {} + hp.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.HOUR] = hp_electricity + hp.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.MONTH] = MonthlyValues.get_total_month( + hp.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.HOUR]) + hp.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.YEAR] = [ + sum(hp.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.MONTH])] + tes.heating_coil_energy_consumption = {} + tes.heating_coil_energy_consumption[cte.HOUR] = q_coil + tes.heating_coil_energy_consumption[cte.MONTH] = MonthlyValues.get_total_month( + tes.heating_coil_energy_consumption[cte.HOUR]) + tes.heating_coil_energy_consumption[cte.YEAR] = [ + sum(tes.heating_coil_energy_consumption[cte.MONTH])] + tes.temperature = t_tank + + + # self.results['DHW Demand (W)'] = demand + # self.results['DHW HP Heat Output (W)'] = q_hp + # self.results['DHW HP Electricity Consumption (W)'] = hp_electricity + # self.results['DHW HP Source Temperature'] = self._t_out + # self.results['DHW HP Supply Temperature'] = t_sup_hp + # self.results['DHW HP COP'] = hp_cop + # self.results['DHW TES Heating Coil Heat Output (W)'] = q_coil + # self.results['DHW TES Temperature'] = t_tank + # self.results['DHW TES Charging Flow Rate (kg/s)'] = m_ch + # self.results['DHW Flow Rate (kg/s)'] = m_dis + # self.results['DHW TES Refill Flow Rate (kg/s)'] = m_refill + # self.results['Available Water in Tank (m3)'] = v_dhw + return hp_electricity, q_coil + + def enrich_buildings(self): + hp_heating, boiler_consumption = self.heating_system_simulation_stratified() + # hp_cooling = self.cooling_system_simulation() + # hp_dhw, heating_coil = self.dhw_system_simulation() + heating_consumption = [hp_heating[i] + boiler_consumption[i] for i in range(len(hp_heating))] + # dhw_consumption = [hp_dhw[i] + heating_coil[i] for i in range(len(hp_dhw))] + # self._building.heating_consumption[cte.HOUR] = heating_consumption + # self._building.heating_consumption[cte.MONTH] = ( + # MonthlyValues.get_total_month(self._building.heating_consumption[cte.HOUR])) + # self._building.heating_consumption[cte.YEAR] = sum(self._building.heating_consumption[cte.MONTH]) + # self._building.cooling_consumption[cte.HOUR] = hp_cooling + # self._building.cooling_consumption[cte.MONTH] = ( + # MonthlyValues.get_total_month(self._building.cooling_consumption[cte.HOUR])) + # self._building.cooling_consumption[cte.YEAR] = sum(self._building.cooling_consumption[cte.MONTH]) + # self._building.domestic_hot_water_consumption[cte.HOUR] = dhw_consumption + # self._building.domestic_hot_water_consumption[cte.MONTH] = ( + # MonthlyValues.get_total_month(self._building.domestic_hot_water_consumption[cte.HOUR])) + # self._building.domestic_hot_water_consumption[cte.YEAR] = ( + # sum(self._building.domestic_hot_water_consumption[cte.MONTH])) + file_name = f'energy_system_simulation_results_{self._name}.csv' + with open(self._output_path / file_name, 'w', newline='') as csvfile: + output_file = csv.writer(csvfile) + # Write header + output_file.writerow(self.results.keys()) + # Write data + output_file.writerows(zip(*self.results.values())) diff --git a/scripts/system_simulation_models/archetypes14_15.py b/scripts/system_simulation_models/archetypes14_15.py new file mode 100644 index 00000000..e3cf52d1 --- /dev/null +++ b/scripts/system_simulation_models/archetypes14_15.py @@ -0,0 +1,402 @@ +import math +import hub.helpers.constants as cte +import csv +from hub.helpers.monthly_values import MonthlyValues + + +class Archetype14_15: + def __init__(self, building, output_path): + self._building = building + self._name = building.name + if 'PV' in building.energy_systems_archetype_name: + i = 1 + self._pv_system = building.energy_systems[0] + else: + i = 0 + self._dhw_system = building.energy_systems[i] + self._heating_system = building.energy_systems[i + 1] + self._cooling_system = building.energy_systems[i + 2] + self._dhw_peak_flow_rate = (building.thermal_zones_from_internal_zones[0].total_floor_area * + building.thermal_zones_from_internal_zones[0].domestic_hot_water.peak_flow * + cte.WATER_DENSITY) + 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 = [demand / cte.WATTS_HOUR_TO_JULES for demand in building.heating_demand[cte.HOUR]] + self._hourly_cooling_demand = [demand / cte.WATTS_HOUR_TO_JULES for demand in building.cooling_demand[cte.HOUR]] + self._hourly_dhw_demand = [demand / cte.WATTS_HOUR_TO_JULES for demand in + building.domestic_hot_water_heat_demand[cte.HOUR]] + self._output_path = output_path + self._t_out = building.external_temperature[cte.HOUR] + self.results = {} + self.dt = 900 + + def heating_system_sizing(self): + storage_factor = 3 + heat_pump = self._heating_system.generation_systems[1] + heat_pump.source_temperature = self._t_out + boiler = self._heating_system.generation_systems[0] + thermal_storage = boiler.energy_storage_systems[0] + heat_pump.nominal_heat_output = round(0.5 * self._heating_peak_load) + boiler.nominal_heat_output = round(0.5 * self._heating_peak_load) + thermal_storage.volume = round( + (self._heating_peak_load * storage_factor * cte.WATTS_HOUR_TO_JULES) / + (cte.WATER_HEAT_CAPACITY * cte.WATER_DENSITY * 25)) + return heat_pump, boiler, thermal_storage + + def cooling_system_sizing(self): + heat_pump = self._cooling_system.generation_systems[0] + heat_pump.nominal_cooling_output = heat_pump.nominal_cooling_output = round(self._cooling_peak_load) + heat_pump.source_temperature = self._t_out + return heat_pump + + + def dhw_system_sizing(self): + storage_factor = 3 + dhw_hp = self._dhw_system.generation_systems[0] + dhw_hp.nominal_heat_output = round(0.7 * self._domestic_hot_water_peak_load) + dhw_hp.source_temperature = self._t_out + dhw_tes = dhw_hp.energy_storage_systems[0] + dhw_tes.volume = round( + (self._domestic_hot_water_peak_load * storage_factor * cte.WATTS_HOUR_TO_JULES) / + (cte.WATER_HEAT_CAPACITY * cte.WATER_DENSITY * 10)) + return dhw_hp, dhw_tes + + def heating_system_simulation(self): + hp, boiler, tes = self.heating_system_sizing() + cop_curve_coefficients = [float(coefficient) for coefficient in hp.heat_efficiency_curve.coefficients] + number_of_ts = int(cte.HOUR_TO_SECONDS / self.dt) + demand = [0] + [x for x in self._hourly_heating_demand for _ in range(number_of_ts)] + t_out = [0] + [x for x in self._t_out for _ in range(number_of_ts)] + hp.source_temperature = self._t_out + variable_names = ["t_sup_hp", "t_tank", "t_ret", "m_ch", "m_dis", "q_hp", "q_boiler", "hp_cop", + "hp_electricity", "boiler_gas_consumption", "t_sup_boiler", "boiler_energy_consumption", + "heating_consumption"] + num_hours = len(demand) + variables = {name: [0] * num_hours for name in variable_names} + (t_sup_hp, t_tank, t_ret, m_ch, m_dis, q_hp, q_boiler, hp_cop, + hp_electricity, boiler_gas_consumption, t_sup_boiler, boiler_energy_consumption, heating_consumption) = \ + [variables[name] for name in variable_names] + t_tank[0] = 55 + hp_heating_cap = hp.nominal_heat_output + boiler_heating_cap = boiler.nominal_heat_output + hp_delta_t = 5 + 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) + # storage temperature prediction + for i in range(len(demand) - 1): + t_tank[i + 1] = (t_tank[i] + + (m_ch[i] * (t_sup_boiler[i] - t_tank[i]) + + (ua * (t_out[i] - t_tank[i])) / cte.WATER_HEAT_CAPACITY - + m_dis[i] * (t_tank[i] - t_ret[i])) * (self.dt / (cte.WATER_DENSITY * v))) + # hp operation + 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 * hp_delta_t) + t_sup_hp[i + 1] = (q_hp[i + 1] / (m_ch[i + 1] * cte.WATER_HEAT_CAPACITY)) + t_tank[i + 1] + elif 40 <= t_tank[i + 1] < 55 and q_hp[i] == 0: + q_hp[i + 1] = 0 + m_ch[i + 1] = 0 + t_sup_hp[i + 1] = t_tank[i + 1] + elif 40 <= 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 * hp_delta_t) + t_sup_hp[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_hp[i + 1] = 0, 0, t_tank[i + 1] + t_sup_hp_fahrenheit = 1.8 * t_sup_hp[i + 1] + 32 + t_out_fahrenheit = 1.8 * t_out[i + 1] + 32 + if q_hp[i + 1] > 0: + hp_cop[i + 1] = (cop_curve_coefficients[0] + + cop_curve_coefficients[1] * t_sup_hp_fahrenheit + + cop_curve_coefficients[2] * t_sup_hp_fahrenheit ** 2 + + cop_curve_coefficients[3] * t_out_fahrenheit + + cop_curve_coefficients[4] * t_out_fahrenheit ** 2 + + cop_curve_coefficients[5] * t_sup_hp_fahrenheit * t_out_fahrenheit) + hp_electricity[i + 1] = q_hp[i + 1] / hp_cop[i + 1] + else: + hp_cop[i + 1] = 0 + hp_electricity[i + 1] = 0 + # boiler operation + if q_hp[i + 1] > 0: + if t_sup_hp[i + 1] < 45: + q_boiler[i + 1] = boiler_heating_cap + elif demand[i + 1] > 0.5 * self._heating_peak_load / self.dt: + q_boiler[i + 1] = 0.5 * boiler_heating_cap + boiler_energy_consumption[i + 1] = q_boiler[i + 1] / boiler_efficiency + boiler_gas_consumption[i + 1] = (q_boiler[i + 1] * self.dt) / (boiler_efficiency * cte.NATURAL_GAS_LHV) + t_sup_boiler[i + 1] = t_sup_hp[i + 1] + (q_boiler[i + 1] / (m_ch[i + 1] * cte.WATER_HEAT_CAPACITY)) + # storage discharging + if demand[i + 1] == 0: + m_dis[i + 1] = 0 + t_ret[i + 1] = t_tank[i + 1] + else: + if demand[i + 1] > 0.5 * self._heating_peak_load / cte.HOUR_TO_SECONDS: + factor = 8 + else: + factor = 4 + m_dis[i + 1] = self._heating_peak_load / (cte.WATER_HEAT_CAPACITY * factor * cte.HOUR_TO_SECONDS) + t_ret[i + 1] = t_tank[i + 1] - demand[i + 1] / (m_dis[i + 1] * cte.WATER_HEAT_CAPACITY) + tes.temperature = [] + hp_electricity_j = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in hp_electricity] + boiler_consumption_j = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in boiler_energy_consumption] + hp_hourly = [] + boiler_hourly = [] + boiler_sum = 0 + hp_sum = 0 + for i in range(1, len(demand)): + hp_sum += hp_electricity_j[i] + boiler_sum += boiler_consumption_j[i] + if (i - 1) % number_of_ts == 0: + tes.temperature.append(t_tank[i]) + hp_hourly.append(hp_sum) + boiler_hourly.append(boiler_sum) + hp_sum = 0 + boiler_sum = 0 + hp.energy_consumption[cte.HEATING] = {} + hp.energy_consumption[cte.HEATING][cte.HOUR] = hp_hourly + hp.energy_consumption[cte.HEATING][cte.MONTH] = MonthlyValues.get_total_month( + hp.energy_consumption[cte.HEATING][cte.HOUR]) + hp.energy_consumption[cte.HEATING][cte.YEAR] = [ + sum(hp.energy_consumption[cte.HEATING][cte.MONTH])] + boiler.energy_consumption[cte.HEATING] = {} + boiler.energy_consumption[cte.HEATING][cte.HOUR] = boiler_hourly + boiler.energy_consumption[cte.HEATING][cte.MONTH] = MonthlyValues.get_total_month( + boiler.energy_consumption[cte.HEATING][cte.HOUR]) + boiler.energy_consumption[cte.HEATING][cte.YEAR] = [ + sum(boiler.energy_consumption[cte.HEATING][cte.MONTH])] + + self.results['Heating Demand (W)'] = demand + self.results['HP Heat Output (W)'] = q_hp + self.results['HP Source Temperature'] = t_out + self.results['HP Supply Temperature'] = t_sup_hp + self.results['HP COP'] = hp_cop + self.results['HP Electricity Consumption (W)'] = hp_electricity + self.results['Boiler Heat Output (W)'] = q_boiler + self.results['Boiler Supply Temperature'] = t_sup_boiler + self.results['Boiler Gas Consumption'] = boiler_gas_consumption + self.results['TES Temperature'] = t_tank + self.results['TES Charging Flow Rate (kg/s)'] = m_ch + self.results['TES Discharge Flow Rate (kg/s)'] = m_dis + self.results['Heating Loop Return Temperature'] = t_ret + return hp_hourly, boiler_hourly + + def cooling_system_simulation(self): + hp = self.cooling_system_sizing() + eer_curve_coefficients = [float(coefficient) for coefficient in hp.cooling_efficiency_curve.coefficients] + cooling_efficiency = float(hp.cooling_efficiency) + number_of_ts = int(cte.HOUR_TO_SECONDS / self.dt) + demand = [0] + [x for x in self._hourly_cooling_demand for _ in range(number_of_ts)] + t_out = [0] + [x for x in self._t_out for _ in range(number_of_ts)] + hp.source_temperature = self._t_out + variable_names = ["t_sup_hp", "t_ret", "m", "q_hp", "hp_electricity", "hp_eer"] + num_hours = len(demand) + variables = {name: [0] * num_hours for name in variable_names} + (t_sup_hp, t_ret, m, q_hp, hp_electricity, hp_eer) = [variables[name] for name in variable_names] + t_ret[0] = 13 + + for i in range(1, len(demand)): + if demand[i] > 0: + m[i] = self._cooling_peak_load / (cte.WATER_HEAT_CAPACITY * 5 * cte.HOUR_TO_SECONDS) + if t_ret[i - 1] >= 13: + if demand[i] < 0.25 * self._cooling_peak_load / cte.HOUR_TO_SECONDS: + q_hp[i] = 0.25 * hp.nominal_cooling_output + elif demand[i] < 0.5 * self._cooling_peak_load / cte.HOUR_TO_SECONDS: + q_hp[i] = 0.5 * hp.nominal_cooling_output + else: + q_hp[i] = hp.nominal_cooling_output + t_sup_hp[i] = t_ret[i - 1] - q_hp[i] / (m[i] * cte.WATER_HEAT_CAPACITY) + else: + q_hp[i] = 0 + t_sup_hp[i] = t_ret[i - 1] + if m[i] == 0: + t_ret[i] = t_sup_hp[i] + else: + t_ret[i] = t_sup_hp[i] + demand[i] / (m[i] * cte.WATER_HEAT_CAPACITY) + else: + m[i] = 0 + q_hp[i] = 0 + t_sup_hp[i] = t_ret[i -1] + t_ret[i] = t_ret[i - 1] + t_sup_hp_fahrenheit = 1.8 * t_sup_hp[i] + 32 + t_out_fahrenheit = 1.8 * t_out[i] + 32 + if q_hp[i] > 0: + hp_eer[i] = (eer_curve_coefficients[0] + + eer_curve_coefficients[1] * t_sup_hp_fahrenheit + + eer_curve_coefficients[2] * t_sup_hp_fahrenheit ** 2 + + eer_curve_coefficients[3] * t_out_fahrenheit + + eer_curve_coefficients[4] * t_out_fahrenheit ** 2 + + eer_curve_coefficients[5] * t_sup_hp_fahrenheit * t_out_fahrenheit) + hp_electricity[i] = q_hp[i] / hp_eer[i] + else: + hp_eer[i] = 0 + hp_electricity[i] = 0 + hp_electricity_j = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in hp_electricity] + hp_hourly = [] + hp_sum = 0 + for i in range(1, len(demand)): + hp_sum += hp_electricity_j[i] + if (i - 1) % number_of_ts == 0: + hp_hourly.append(hp_sum) + hp_sum = 0 + hp.energy_consumption[cte.COOLING] = {} + hp.energy_consumption[cte.COOLING][cte.HOUR] = hp_hourly + hp.energy_consumption[cte.COOLING][cte.MONTH] = MonthlyValues.get_total_month( + hp.energy_consumption[cte.COOLING][cte.HOUR]) + hp.energy_consumption[cte.COOLING][cte.YEAR] = [ + sum(hp.energy_consumption[cte.COOLING][cte.MONTH])] + self.results['Cooling Demand (W)'] = demand + self.results['HP Cooling Output (W)'] = q_hp + self.results['HP Cooling Supply Temperature'] = t_sup_hp + self.results['HP Cooling COP'] = hp_eer + self.results['HP Electricity Consumption'] = hp_electricity + self.results['Cooling Loop Flow Rate (kg/s)'] = m + self.results['Cooling Loop Return Temperature'] = t_ret + return hp_hourly + + def dhw_system_simulation(self): + hp, tes = self.dhw_system_sizing() + cop_curve_coefficients = [float(coefficient) for coefficient in hp.heat_efficiency_curve.coefficients] + number_of_ts = int(cte.HOUR_TO_SECONDS / self.dt) + demand = [0] + [x for x in self._hourly_dhw_demand for _ in range(number_of_ts)] + t_out = [0] + [x for x in self._t_out for _ in range(number_of_ts)] + variable_names = ["t_sup_hp", "t_tank", "m_ch", "m_dis", "q_hp", "q_coil", "hp_cop", + "hp_electricity", "available hot water (m3)", "refill flow rate (kg/s)"] + num_hours = len(demand) + variables = {name: [0] * num_hours for name in variable_names} + (t_sup_hp, t_tank, m_ch, m_dis, m_refill, q_hp, q_coil, hp_cop, hp_electricity, v_dhw) = \ + [variables[name] for name in variable_names] + t_tank[0] = 70 + v_dhw[0] = tes.volume + + hp_heating_cap = hp.nominal_heat_output + hp_delta_t = 8 + 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) + freshwater_temperature = 18 + for i in range(len(demand) - 1): + delta_t_demand = demand[i] * (self.dt / (cte.WATER_DENSITY * cte.WATER_HEAT_CAPACITY * v)) + if t_tank[i] < 65: + q_hp[i] = hp_heating_cap + delta_t_hp = q_hp[i] * (self.dt / (cte.WATER_DENSITY * cte.WATER_HEAT_CAPACITY * v)) + if demand[i] > 0: + dhw_needed = (demand[i] * cte.HOUR_TO_SECONDS) / (cte.WATER_HEAT_CAPACITY * t_tank[i] * cte.WATER_DENSITY) + m_dis[i] = dhw_needed * cte.WATER_DENSITY / cte.HOUR_TO_SECONDS + m_refill[i] = m_dis[i] + delta_t_freshwater = m_refill[i] * (t_tank[i] - freshwater_temperature) * (self.dt / (v * cte.WATER_DENSITY)) + diff = delta_t_freshwater + delta_t_demand - delta_t_hp + if diff > 0: + if diff > 0: + power = diff * (cte.WATER_DENSITY * cte.WATER_HEAT_CAPACITY * v) / self.dt + if power <= float(tes.heating_coil_capacity): + q_coil[i] = power + else: + q_coil[i] = float(tes.heating_coil_capacity) + delta_t_coil = q_coil[i] * (self.dt / (cte.WATER_DENSITY * cte.WATER_HEAT_CAPACITY * v)) + + if q_hp[i] > 0: + m_ch[i] = q_hp[i] / (cte.WATER_HEAT_CAPACITY * hp_delta_t) + t_sup_hp[i] = (q_hp[i] / (m_ch[i] * cte.WATER_HEAT_CAPACITY)) + t_tank[i] + else: + m_ch[i] = 0 + t_sup_hp[i] = t_tank[i] + t_sup_hp_fahrenheit = 1.8 * t_sup_hp[i] + 32 + t_out_fahrenheit = 1.8 * t_out[i] + 32 + if q_hp[i] > 0: + hp_cop[i] = (cop_curve_coefficients[0] + + cop_curve_coefficients[1] * t_sup_hp_fahrenheit + + cop_curve_coefficients[2] * t_sup_hp_fahrenheit ** 2 + + cop_curve_coefficients[3] * t_out_fahrenheit + + cop_curve_coefficients[4] * t_out_fahrenheit ** 2 + + cop_curve_coefficients[5] * t_sup_hp_fahrenheit * t_out_fahrenheit) + hp_electricity[i] = q_hp[i] / hp_cop[i] + else: + hp_cop[i] = 0 + hp_electricity[i] = 0 + + t_tank[i + 1] = t_tank[i] + (delta_t_hp - delta_t_freshwater - delta_t_demand + delta_t_coil) + tes.temperature = [] + hp_electricity_j = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in hp_electricity] + heating_coil_j = [(x * cte.WATTS_HOUR_TO_JULES) / number_of_ts for x in q_coil] + hp_hourly = [] + coil_hourly = [] + coil_sum = 0 + hp_sum = 0 + for i in range(1, len(demand)): + hp_sum += hp_electricity_j[i] + coil_sum += heating_coil_j[i] + if (i - 1) % number_of_ts == 0: + tes.temperature.append(t_tank[i]) + hp_hourly.append(hp_sum) + coil_hourly.append(coil_sum) + hp_sum = 0 + coil_sum = 0 + + hp.energy_consumption[cte.DOMESTIC_HOT_WATER] = {} + hp.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.HOUR] = hp_hourly + hp.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.MONTH] = MonthlyValues.get_total_month( + hp.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.HOUR]) + hp.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.YEAR] = [ + sum(hp.energy_consumption[cte.DOMESTIC_HOT_WATER][cte.MONTH])] + tes.heating_coil_energy_consumption = {} + tes.heating_coil_energy_consumption[cte.HOUR] = coil_hourly + tes.heating_coil_energy_consumption[cte.MONTH] = MonthlyValues.get_total_month( + tes.heating_coil_energy_consumption[cte.HOUR]) + tes.heating_coil_energy_consumption[cte.YEAR] = [ + sum(tes.heating_coil_energy_consumption[cte.MONTH])] + tes.temperature = t_tank + + self.results['DHW Demand (W)'] = demand + self.results['DHW HP Heat Output (W)'] = q_hp + self.results['DHW HP Electricity Consumption (W)'] = hp_electricity + self.results['DHW HP Source Temperature'] = t_out + self.results['DHW HP Supply Temperature'] = t_sup_hp + self.results['DHW HP COP'] = hp_cop + self.results['DHW TES Heating Coil Heat Output (W)'] = q_coil + self.results['DHW TES Temperature'] = t_tank + self.results['DHW TES Charging Flow Rate (kg/s)'] = m_ch + self.results['DHW Flow Rate (kg/s)'] = m_dis + self.results['DHW TES Refill Flow Rate (kg/s)'] = m_refill + self.results['Available Water in Tank (m3)'] = v_dhw + return hp_hourly, coil_hourly + + + def enrich_buildings(self): + hp_heating, boiler_consumption = self.heating_system_simulation() + hp_cooling = self.cooling_system_simulation() + hp_dhw, heating_coil = self.dhw_system_simulation() + heating_consumption = [hp_heating[i] + boiler_consumption[i] for i in range(len(hp_heating))] + dhw_consumption = [hp_dhw[i] + heating_coil[i] for i in range(len(hp_dhw))] + self._building.heating_consumption[cte.HOUR] = heating_consumption + self._building.heating_consumption[cte.MONTH] = ( + MonthlyValues.get_total_month(self._building.heating_consumption[cte.HOUR])) + self._building.heating_consumption[cte.YEAR] = [sum(self._building.heating_consumption[cte.MONTH])] + self._building.cooling_consumption[cte.HOUR] = hp_cooling + self._building.cooling_consumption[cte.MONTH] = ( + MonthlyValues.get_total_month(self._building.cooling_consumption[cte.HOUR])) + self._building.cooling_consumption[cte.YEAR] = [sum(self._building.cooling_consumption[cte.MONTH])] + self._building.domestic_hot_water_consumption[cte.HOUR] = dhw_consumption + self._building.domestic_hot_water_consumption[cte.MONTH] = ( + MonthlyValues.get_total_month(self._building.domestic_hot_water_consumption[cte.HOUR])) + self._building.domestic_hot_water_consumption[cte.YEAR] = ( + sum(self._building.domestic_hot_water_consumption[cte.MONTH])) + file_name = f'energy_system_simulation_results_{self._name}.csv' + with open(self._output_path / file_name, 'w', newline='') as csvfile: + output_file = csv.writer(csvfile) + # Write header + output_file.writerow(self.results.keys()) + # Write data + output_file.writerows(zip(*self.results.values())) diff --git a/simulation_result_test.py b/simulation_result_test.py new file mode 100644 index 00000000..a54477b4 --- /dev/null +++ b/simulation_result_test.py @@ -0,0 +1,67 @@ +from pathlib import Path +import subprocess +from scripts.ep_run_enrich import energy_plus_workflow +from hub.imports.geometry_factory import GeometryFactory +from hub.helpers.dictionaries import Dictionaries +from hub.imports.construction_factory import ConstructionFactory +from hub.imports.usage_factory import UsageFactory +from hub.imports.weather_factory import WeatherFactory +from hub.imports.results_factory import ResultFactory +from scripts.energy_system_retrofit_report import EnergySystemRetrofitReport +from scripts.geojson_creator import process_geojson +from scripts import random_assignation +from hub.imports.energy_systems_factory import EnergySystemsFactory +from scripts.energy_system_sizing import SystemSizing +from scripts.solar_angles import CitySolarAngles +from scripts.pv_sizing_and_simulation import PVSizingSimulation +from scripts.energy_system_retrofit_results import consumption_data, cost_data +from scripts.energy_system_sizing_and_simulation_factory import EnergySystemsSimulationFactory +from scripts.costs.cost import Cost +from scripts.costs.constants import SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV, SYSTEM_RETROFIT_AND_PV, CURRENT_STATUS +import hub.helpers.constants as cte +from hub.exports.exports_factory import ExportsFactory +from scripts.pv_feasibility import pv_feasibility + +# Specify the GeoJSON file path +input_files_path = (Path(__file__).parent / 'input_files') +input_files_path.mkdir(parents=True, exist_ok=True) +geojson_file = process_geojson(x=-73.5681295982132, y=45.49218262677643, diff=0.0001) +geojson_file_path = input_files_path / 'output_buildings.geojson' +output_path = (Path(__file__).parent / 'out_files').resolve() +output_path.mkdir(parents=True, exist_ok=True) +energy_plus_output_path = output_path / 'energy_plus_outputs' +energy_plus_output_path.mkdir(parents=True, exist_ok=True) +simulation_results_path = (Path(__file__).parent / 'out_files' / 'simulation_results').resolve() +simulation_results_path.mkdir(parents=True, exist_ok=True) +sra_output_path = output_path / 'sra_outputs' +sra_output_path.mkdir(parents=True, exist_ok=True) +cost_analysis_output_path = output_path / 'cost_analysis' +cost_analysis_output_path.mkdir(parents=True, exist_ok=True) +city = GeometryFactory(file_type='geojson', + path=geojson_file_path, + height_field='height', + year_of_construction_field='year_of_construction', + function_field='function', + function_to_hub=Dictionaries().montreal_function_to_hub_function).city +ConstructionFactory('nrcan', city).enrich() +UsageFactory('nrcan', city).enrich() +WeatherFactory('epw', city).enrich() +energy_plus_workflow(city, energy_plus_output_path) +random_assignation.call_random(city.buildings, random_assignation.residential_systems_percentage) +EnergySystemsFactory('montreal_custom', city).enrich() +SystemSizing(city.buildings).montreal_custom() +for i in range(12): + monthly_cooling = 0 + for building in city.buildings: + monthly_cooling += building.cooling_consumption[cte.MONTH][i] / (cte.WATTS_HOUR_TO_JULES * 1000) + print(monthly_cooling) +random_assignation.call_random(city.buildings, random_assignation.residential_new_systems_percentage) +EnergySystemsFactory('montreal_future', city).enrich() +for building in city.buildings: + if building.energy_systems_archetype_name == 'PV+4Pipe+DHW': + EnergySystemsSimulationFactory('archetype13', building=building, output_path=simulation_results_path).enrich() +for i in range(12): + monthly_cooling = 0 + for building in city.buildings: + monthly_cooling += building.cooling_consumption[cte.MONTH][i] / (cte.WATTS_HOUR_TO_JULES * 1000) + print(monthly_cooling) diff --git a/tests/test_construction_factory.py b/tests/test_construction_factory.py index 710894bd..a340594a 100644 --- a/tests/test_construction_factory.py +++ b/tests/test_construction_factory.py @@ -81,7 +81,7 @@ class TestConstructionFactory(TestCase): self.assertEqual(len(building.external_temperature), 0, 'building external temperature is calculated') self.assertEqual(len(building.global_horizontal), 0, 'building global horizontal is calculated') self.assertEqual(len(building.diffuse), 0, 'building diffuse is calculated') - self.assertEqual(len(building.beam), 0, 'building beam is calculated') + self.assertEqual(len(building.direct_normal), 0, 'building beam is calculated') self.assertIsNotNone(building.lower_corner, 'building lower corner is none') self.assertEqual(len(building.sensors), 0, 'building sensors are assigned') self.assertIsNotNone(building.internal_zones, 'no internal zones created') diff --git a/tests/test_geometry_factory.py b/tests/test_geometry_factory.py index 3b5bd8f8..d66c815f 100644 --- a/tests/test_geometry_factory.py +++ b/tests/test_geometry_factory.py @@ -52,7 +52,7 @@ class TestGeometryFactory(TestCase): self.assertEqual(len(building.external_temperature), 0, 'building external temperature is calculated') self.assertEqual(len(building.global_horizontal), 0, 'building global horizontal is calculated') self.assertEqual(len(building.diffuse), 0, 'building diffuse is calculated') - self.assertEqual(len(building.beam), 0, 'building beam is calculated') + self.assertEqual(len(building.direct_normal), 0, 'building beam is calculated') self.assertIsNotNone(building.lower_corner, 'building lower corner is none') self.assertEqual(len(building.sensors), 0, 'building sensors are assigned') self.assertIsNotNone(building.internal_zones, 'no internal zones created') diff --git a/tests/test_systems_catalog.py b/tests/test_systems_catalog.py index 839107c2..612a8fe6 100644 --- a/tests/test_systems_catalog.py +++ b/tests/test_systems_catalog.py @@ -38,10 +38,10 @@ class TestSystemsCatalog(TestCase): catalog = EnergySystemsCatalogFactory('montreal_future').catalog catalog_categories = catalog.names() - archetypes = catalog.names('archetypes') - self.assertEqual(13, len(archetypes['archetypes'])) + archetypes = catalog.names() + self.assertEqual(15, len(archetypes['archetypes'])) systems = catalog.names('systems') - self.assertEqual(10, len(systems['systems'])) + self.assertEqual(12, len(systems['systems'])) generation_equipments = catalog.names('generation_equipments') self.assertEqual(27, len(generation_equipments['generation_equipments'])) with self.assertRaises(ValueError): diff --git a/tests/test_usage_factory.py b/tests/test_usage_factory.py index 6b6d821a..49cb45db 100644 --- a/tests/test_usage_factory.py +++ b/tests/test_usage_factory.py @@ -44,7 +44,7 @@ class TestUsageFactory(TestCase): self.assertEqual(len(building.external_temperature), 0, 'building external temperature is calculated') self.assertEqual(len(building.global_horizontal), 0, 'building global horizontal is calculated') self.assertEqual(len(building.diffuse), 0, 'building diffuse is calculated') - self.assertEqual(len(building.beam), 0, 'building beam is calculated') + self.assertEqual(len(building.direct_normal), 0, 'building beam is calculated') self.assertIsNotNone(building.lower_corner, 'building lower corner is none') self.assertEqual(len(building.sensors), 0, 'building sensors are assigned') self.assertIsNotNone(building.internal_zones, 'no internal zones created')