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')