diff --git a/building_modelling/geojson_creator.py b/building_modelling/geojson_creator.py
index c96c340d..2ea2577a 100644
--- a/building_modelling/geojson_creator.py
+++ b/building_modelling/geojson_creator.py
@@ -4,16 +4,16 @@ from shapely import Point
from pathlib import Path
-def process_geojson(x, y, diff, expansion=False):
+def process_geojson(x, y, diff, path, 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()
+ geojson_file = Path(path / 'data/collinear_clean 2.geojson').resolve()
if not expansion:
- output_file = Path('./input_files/output_buildings.geojson').resolve()
+ output_file = Path(path / 'input_files/output_buildings.geojson').resolve()
else:
- output_file = Path('./input_files/output_buildings_expanded.geojson').resolve()
+ output_file = Path(path / 'input_files/output_buildings_expanded.geojson').resolve()
buildings_in_region = []
with open(geojson_file, 'r') as file:
diff --git a/energy_system_modelling_package/energy_system_modelling_factories/archetypes/montreal/archetype_cluster_1.py b/energy_system_modelling_package/energy_system_modelling_factories/archetypes/montreal/archetype_cluster_1.py
index dfe6f973..af6bd75e 100644
--- a/energy_system_modelling_package/energy_system_modelling_factories/archetypes/montreal/archetype_cluster_1.py
+++ b/energy_system_modelling_package/energy_system_modelling_factories/archetypes/montreal/archetype_cluster_1.py
@@ -6,8 +6,6 @@ from energy_system_modelling_package.energy_system_modelling_factories.hvac_dhw_
HeatPumpCooling
from energy_system_modelling_package.energy_system_modelling_factories.hvac_dhw_systems_simulation_models.domestic_hot_water_heat_pump_with_tes import \
DomesticHotWaterHeatPumpTes
-from energy_system_modelling_package.energy_system_modelling_factories.pv_assessment.pv_model import PVModel
-from energy_system_modelling_package.energy_system_modelling_factories.pv_assessment.electricity_demand_calculator import HourlyElectricityDemand
import hub.helpers.constants as cte
from hub.helpers.monthly_values import MonthlyValues
@@ -21,10 +19,6 @@ class ArchetypeCluster1:
self.heating_results, self.building_heating_hourly_consumption = self.heating_system_simulation()
self.cooling_results, self.total_cooling_consumption_hourly = self.cooling_system_simulation()
self.dhw_results, self.total_dhw_consumption_hourly = self.dhw_system_simulation()
- if 'PV' in self.building.energy_systems_archetype_name:
- self.pv_results = self.pv_system_simulation()
- else:
- self.pv_results = None
def heating_system_simulation(self):
building_heating_hourly_consumption = []
@@ -55,7 +49,7 @@ class ArchetypeCluster1:
return results, building_heating_hourly_consumption
def cooling_system_simulation(self):
- hp = self.building.energy_systems[1].generation_systems[1]
+ hp = self.building.energy_systems[2].generation_systems[0]
cooling_demand_joules = self.building.cooling_demand[cte.HOUR]
cooling_peak_load = self.building.cooling_peak_load[cte.YEAR][0]
cutoff_temperature = 13
@@ -71,8 +65,8 @@ class ArchetypeCluster1:
def dhw_system_simulation(self):
building_dhw_hourly_consumption = []
- hp = self.building.energy_systems[2].generation_systems[0]
- tes = self.building.energy_systems[2].generation_systems[0].energy_storage_systems[0]
+ hp = self.building.energy_systems[-1].generation_systems[0]
+ tes = self.building.energy_systems[-1].generation_systems[0].energy_storage_systems[0]
dhw_demand_joules = self.building.domestic_hot_water_heat_demand[cte.HOUR]
upper_limit_tes = 65
outdoor_temperature = self.building.external_temperature[cte.HOUR]
@@ -93,18 +87,6 @@ class ArchetypeCluster1:
dhw_consumption = 0
return results, building_dhw_hourly_consumption
- def pv_system_simulation(self):
- results = None
- pv = self.building.energy_systems[0].generation_systems[0]
- hourly_electricity_demand = HourlyElectricityDemand(self.building).calculate()
- model_type = 'fixed_efficiency'
- if model_type == 'fixed_efficiency':
- results = PVModel(pv=pv,
- hourly_electricity_demand_joules=hourly_electricity_demand,
- solar_radiation=self.building.roofs[0].global_irradiance_tilted[cte.HOUR],
- installed_pv_area=self.building.roofs[0].installed_solar_collector_area,
- model_type='fixed_efficiency').fixed_efficiency()
- return results
def enrich_building(self):
results = self.heating_results | self.cooling_results | self.dhw_results
@@ -121,19 +103,6 @@ class ArchetypeCluster1:
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])]
- if self.pv_results is not None:
- self.building.onsite_electrical_production[cte.HOUR] = [x * cte.WATTS_HOUR_TO_JULES for x in
- self.pv_results['PV Output (W)']]
- 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])]
- if self.csv_output:
- file_name = f'pv_system_simulation_results_{self.building.name}.csv'
- with open(self.output_path / file_name, 'w', newline='') as csvfile:
- output_file = csv.writer(csvfile)
- # Write header
- output_file.writerow(self.pv_results.keys())
- # Write data
- output_file.writerows(zip(*self.pv_results.values()))
if self.csv_output:
file_name = f'energy_system_simulation_results_{self.building.name}.csv'
with open(self.output_path / file_name, 'w', newline='') as csvfile:
diff --git a/energy_system_modelling_package/energy_system_modelling_factories/energy_system_sizing_factory.py b/energy_system_modelling_package/energy_system_modelling_factories/energy_system_sizing_factory.py
index eab3c9f2..faca2191 100644
--- a/energy_system_modelling_package/energy_system_modelling_factories/energy_system_sizing_factory.py
+++ b/energy_system_modelling_package/energy_system_modelling_factories/energy_system_sizing_factory.py
@@ -9,7 +9,6 @@ from energy_system_modelling_package.energy_system_modelling_factories.system_si
PeakLoadSizing
from energy_system_modelling_package.energy_system_modelling_factories.system_sizing_methods.heuristic_sizing import \
HeuristicSizing
-from energy_system_modelling_package.energy_system_modelling_factories.pv_assessment.pv_sizing import PVSizing
class EnergySystemsSizingFactory:
@@ -39,33 +38,6 @@ class EnergySystemsSizingFactory:
for building in self._city.buildings:
building.level_of_detail.energy_systems = 1
- def _pv_sizing(self):
- """
- Size rooftop, facade or mixture of them for buildings
- """
- system_type = 'rooftop'
- results = {}
- if system_type == 'rooftop':
- surface_azimuth = 180
- maintenance_factor = 0.1
- mechanical_equipment_factor = 0.3
- orientation_factor = 0.1
- tilt_angle = self._city.latitude
- pv_sizing = PVSizing(self._city,
- tilt_angle=tilt_angle,
- surface_azimuth=surface_azimuth,
- mechanical_equipment_factor=mechanical_equipment_factor,
- maintenance_factor=maintenance_factor,
- orientation_factor=orientation_factor,
- system_type=system_type)
- results = pv_sizing.rooftop_sizing()
- pv_sizing.rooftop_tilted_radiation()
-
- self._city.level_of_detail.energy_systems = 1
- for building in self._city.buildings:
- building.level_of_detail.energy_systems = 1
- return results
-
def _district_heating_cooling_sizing(self):
"""
Size District Heating and Cooling Network
diff --git a/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/electricity_demand_calculator.py b/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/electricity_demand_calculator.py
index 175f367e..961f5d79 100644
--- a/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/electricity_demand_calculator.py
+++ b/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/electricity_demand_calculator.py
@@ -6,8 +6,8 @@ class HourlyElectricityDemand:
def calculate(self):
hourly_electricity_consumption = []
energy_systems = self.building.energy_systems
- appliance = self.building.appliances_electrical_demand[cte.HOUR]
- lighting = self.building.lighting_electrical_demand[cte.HOUR]
+ appliance = self.building.appliances_electrical_demand[cte.HOUR] if self.building.appliances_electrical_demand else 0
+ lighting = self.building.lighting_electrical_demand[cte.HOUR] if self.building.lighting_electrical_demand else 0
elec_heating = 0
elec_cooling = 0
elec_dhw = 0
@@ -59,10 +59,12 @@ class HourlyElectricityDemand:
else:
cooling = self.building.cooling_consumption[cte.HOUR]
- for i in range(len(self.building.heating_demand[cte.HOUR])):
+ for i in range(8760):
hourly = 0
- hourly += appliance[i]
- hourly += lighting[i]
+ if isinstance(appliance, list):
+ hourly += appliance[i]
+ if isinstance(lighting, list):
+ hourly += lighting[i]
if heating is not None:
hourly += heating[i]
if cooling is not None:
diff --git a/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/pv_feasibility.py b/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/pv_feasibility.py
deleted file mode 100644
index 9278b16c..00000000
--- a/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/pv_feasibility.py
+++ /dev/null
@@ -1,37 +0,0 @@
-from pathlib import Path
-import subprocess
-from hub.imports.geometry_factory import GeometryFactory
-from building_modelling.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.parent.parent / 'input_files')
- output_path = (Path(__file__).parent.parent.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/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/pv_model.py b/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/pv_model.py
deleted file mode 100644
index 2714befa..00000000
--- a/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/pv_model.py
+++ /dev/null
@@ -1,42 +0,0 @@
-import math
-import hub.helpers.constants as cte
-from hub.helpers.monthly_values import MonthlyValues
-
-
-class PVModel:
- def __init__(self, pv, hourly_electricity_demand_joules, solar_radiation, installed_pv_area, model_type, ns=None,
- np=None):
- self.pv = pv
- self.hourly_electricity_demand = [demand / cte.WATTS_HOUR_TO_JULES for demand in hourly_electricity_demand_joules]
- self.solar_radiation = solar_radiation
- self.installed_pv_area = installed_pv_area
- self._model_type = '_' + model_type.lower()
- self.ns = ns
- self.np = np
- self.results = {}
-
- def fixed_efficiency(self):
- module_efficiency = float(self.pv.electricity_efficiency)
- variable_names = ["pv_output", "import", "export", "self_sufficiency_ratio"]
- variables = {name: [0] * len(self.hourly_electricity_demand) for name in variable_names}
- (pv_out, grid_import, grid_export, self_sufficiency_ratio) = [variables[name] for name in variable_names]
- for i in range(len(self.hourly_electricity_demand)):
- pv_out[i] = module_efficiency * self.installed_pv_area * self.solar_radiation[i] / cte.WATTS_HOUR_TO_JULES
- if pv_out[i] < self.hourly_electricity_demand[i]:
- grid_import[i] = self.hourly_electricity_demand[i] - pv_out[i]
- else:
- grid_export[i] = pv_out[i] - self.hourly_electricity_demand[i]
- self_sufficiency_ratio[i] = pv_out[i] / self.hourly_electricity_demand[i]
- self.results['Electricity Demand (W)'] = self.hourly_electricity_demand
- self.results['PV Output (W)'] = pv_out
- self.results['Imported from Grid (W)'] = grid_import
- self.results['Exported to Grid (W)'] = grid_export
- self.results['Self Sufficiency Ratio'] = self_sufficiency_ratio
- return self.results
-
- def enrich(self):
- """
- Enrich the city given to the class using the class given handler
- :return: None
- """
- return getattr(self, self._model_type, lambda: None)()
diff --git a/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/pv_sizing.py b/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/pv_sizing.py
deleted file mode 100644
index b53ba465..00000000
--- a/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/pv_sizing.py
+++ /dev/null
@@ -1,70 +0,0 @@
-import math
-import hub.helpers.constants as cte
-from energy_system_modelling_package.energy_system_modelling_factories.pv_assessment.solar_angles import CitySolarAngles
-from energy_system_modelling_package.energy_system_modelling_factories.pv_assessment.radiation_tilted import RadiationTilted
-
-
-class PVSizing(CitySolarAngles):
- def __init__(self, city, tilt_angle, surface_azimuth=180, maintenance_factor=0.1, mechanical_equipment_factor=0.3,
- orientation_factor=0.1, system_type='rooftop'):
- super().__init__(location_latitude=city.latitude,
- location_longitude=city.longitude,
- tilt_angle=tilt_angle,
- surface_azimuth_angle=surface_azimuth)
- self.city = city
- self.maintenance_factor = maintenance_factor
- self.mechanical_equipment_factor = mechanical_equipment_factor
- self.orientation_factor = orientation_factor
- self.angles = self.calculate
- self.system_type = system_type
-
- def rooftop_sizing(self):
- results = {}
- # Available Roof Area
- for building in self.city.buildings:
- for energy_system in building.energy_systems:
- for generation_system in energy_system.generation_systems:
- if generation_system.system_type == cte.PHOTOVOLTAIC:
- module_width = float(generation_system.width)
- module_height = float(generation_system.height)
- roof_area = 0
- for roof in building.roofs:
- roof_area += roof.perimeter_area
- pv_module_area = module_width * module_height
- available_roof = ((self.maintenance_factor + self.orientation_factor + self.mechanical_equipment_factor) *
- roof_area)
- # Inter-Row Spacing
- winter_solstice = self.angles[(self.angles['AST'].dt.month == 12) &
- (self.angles['AST'].dt.day == 21) &
- (self.angles['AST'].dt.hour == 12)]
- solar_altitude = winter_solstice['solar altitude'].values[0]
- solar_azimuth = winter_solstice['solar azimuth'].values[0]
- distance = ((module_height * abs(math.cos(math.radians(solar_azimuth)))) /
- math.tan(math.radians(solar_altitude)))
- distance = float(format(distance, '.1f'))
- # Calculation of the number of panels
- space_dimension = math.sqrt(available_roof)
- space_dimension = float(format(space_dimension, '.2f'))
- panels_per_row = math.ceil(space_dimension / module_width)
- number_of_rows = math.ceil(space_dimension / distance)
- total_number_of_panels = panels_per_row * number_of_rows
- total_pv_area = panels_per_row * number_of_rows * pv_module_area
- building.roofs[0].installed_solar_collector_area = total_pv_area
- results[f'Building {building.name}'] = {'total_roof_area': roof_area,
- 'PV dedicated area': available_roof,
- 'total_pv_area': total_pv_area,
- 'total_number_of_panels': total_number_of_panels,
- 'number_of_rows': number_of_rows,
- 'panels_per_row': panels_per_row}
- return results
-
- def rooftop_tilted_radiation(self):
- for building in self.city.buildings:
- RadiationTilted(building=building,
- solar_angles=self.angles,
- tilt_angle=self.tilt_angle,
- ghi=building.roofs[0].global_irradiance[cte.HOUR],
- ).enrich()
-
- def facade_sizing(self):
- pass
diff --git a/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/pv_sizing_and_simulation.py b/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/pv_sizing_and_simulation.py
deleted file mode 100644
index fc26fe7e..00000000
--- a/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/pv_sizing_and_simulation.py
+++ /dev/null
@@ -1,59 +0,0 @@
-import math
-
-from energy_system_modelling_package.energy_system_modelling_factories.pv_assessment.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_constant_efficiency(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/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/pv_system_assessment.py b/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/pv_system_assessment.py
new file mode 100644
index 00000000..ce209581
--- /dev/null
+++ b/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/pv_system_assessment.py
@@ -0,0 +1,225 @@
+import math
+import csv
+import hub.helpers.constants as cte
+from energy_system_modelling_package.energy_system_modelling_factories.pv_assessment.electricity_demand_calculator import \
+ HourlyElectricityDemand
+from hub.catalog_factories.energy_systems_catalog_factory import EnergySystemsCatalogFactory
+from hub.helpers.monthly_values import MonthlyValues
+
+
+class PvSystemAssessment:
+ def __init__(self, building=None, pv_system=None, battery=None, electricity_demand=None, tilt_angle=None,
+ solar_angles=None, pv_installation_type=None, simulation_model_type=None, module_model_name=None,
+ inverter_efficiency=None, system_catalogue_handler=None, roof_percentage_coverage=None,
+ facade_coverage_percentage=None, csv_output=False, output_path=None):
+ """
+ :param building:
+ :param tilt_angle:
+ :param solar_angles:
+ :param simulation_model_type:
+ :param module_model_name:
+ :param inverter_efficiency:
+ :param system_catalogue_handler:
+ :param roof_percentage_coverage:
+ :param facade_coverage_percentage:
+ """
+ self.building = building
+ self.electricity_demand = electricity_demand
+ self.tilt_angle = tilt_angle
+ self.solar_angles = solar_angles
+ self.pv_installation_type = pv_installation_type
+ self.simulation_model_type = simulation_model_type
+ self.module_model_name = module_model_name
+ self.inverter_efficiency = inverter_efficiency
+ self.system_catalogue_handler = system_catalogue_handler
+ self.roof_percentage_coverage = roof_percentage_coverage
+ self.facade_coverage_percentage = facade_coverage_percentage
+ self.pv_hourly_generation = None
+ self.t_cell = None
+ self.results = {}
+ self.csv_output = csv_output
+ self.output_path = output_path
+ if pv_system is not None:
+ self.pv_system = pv_system
+ else:
+ for energy_system in self.building.energy_systems:
+ for generation_system in energy_system.generation_systems:
+ if generation_system.system_type == cte.PHOTOVOLTAIC:
+ self.pv_system = generation_system
+ if battery is not None:
+ self.battery = battery
+ else:
+ for energy_system in self.building.energy_systems:
+ for generation_system in energy_system.generation_systems:
+ if generation_system.system_type == cte.PHOTOVOLTAIC and generation_system.energy_storage_systems is not None:
+ for storage_system in generation_system.energy_storage_systems:
+ if storage_system.type_energy_stored == cte.ELECTRICAL:
+ self.battery = storage_system
+
+ @staticmethod
+ def explicit_model(pv_system, inverter_efficiency, number_of_panels, irradiance, outdoor_temperature):
+ inverter_efficiency = inverter_efficiency
+ stc_power = float(pv_system.standard_test_condition_maximum_power)
+ stc_irradiance = float(pv_system.standard_test_condition_radiation)
+ cell_temperature_coefficient = float(pv_system.cell_temperature_coefficient) / 100 if (
+ pv_system.cell_temperature_coefficient is not None) else None
+ stc_t_cell = float(pv_system.standard_test_condition_cell_temperature)
+ nominal_condition_irradiance = float(pv_system.nominal_radiation)
+ nominal_condition_cell_temperature = float(pv_system.nominal_cell_temperature)
+ nominal_t_out = float(pv_system.nominal_ambient_temperature)
+ g_i = irradiance
+ t_out = outdoor_temperature
+ t_cell = []
+ pv_output = []
+ for i in range(len(g_i)):
+ t_cell.append((t_out[i] + (g_i[i] / nominal_condition_irradiance) *
+ (nominal_condition_cell_temperature - nominal_t_out)))
+ pv_output.append((inverter_efficiency * number_of_panels * (stc_power * (g_i[i] / stc_irradiance) *
+ (1 - cell_temperature_coefficient *
+ (t_cell[i] - stc_t_cell)))))
+ return pv_output
+
+ def rooftop_sizing(self):
+ pv_system = self.pv_system
+ if self.module_model_name is not None:
+ self.system_assignation()
+ # System Sizing
+ module_width = float(pv_system.width)
+ module_height = float(pv_system.height)
+ roof_area = 0
+ for roof in self.building.roofs:
+ roof_area += roof.perimeter_area
+ pv_module_area = module_width * module_height
+ available_roof = (self.roof_percentage_coverage * roof_area)
+ # Inter-Row Spacing
+ winter_solstice = self.solar_angles[(self.solar_angles['AST'].dt.month == 12) &
+ (self.solar_angles['AST'].dt.day == 21) &
+ (self.solar_angles['AST'].dt.hour == 12)]
+ solar_altitude = winter_solstice['solar altitude'].values[0]
+ solar_azimuth = winter_solstice['solar azimuth'].values[0]
+ distance = ((module_height * math.sin(math.radians(self.tilt_angle)) * abs(
+ math.cos(math.radians(solar_azimuth)))) / math.tan(math.radians(solar_altitude)))
+ distance = float(format(distance, '.2f'))
+ # Calculation of the number of panels
+ space_dimension = math.sqrt(available_roof)
+ space_dimension = float(format(space_dimension, '.2f'))
+ panels_per_row = math.ceil(space_dimension / module_width)
+ number_of_rows = math.ceil(space_dimension / distance)
+ total_number_of_panels = panels_per_row * number_of_rows
+ total_pv_area = total_number_of_panels * pv_module_area
+ self.building.roofs[0].installed_solar_collector_area = total_pv_area
+ return panels_per_row, number_of_rows
+
+ def system_assignation(self):
+ generation_units_catalogue = EnergySystemsCatalogFactory(self.system_catalogue_handler).catalog
+ catalog_pv_generation_equipments = [component for component in
+ generation_units_catalogue.entries('generation_equipments') if
+ component.system_type == 'photovoltaic']
+ selected_pv_module = None
+ for pv_module in catalog_pv_generation_equipments:
+ if self.module_model_name == pv_module.model_name:
+ selected_pv_module = pv_module
+ if selected_pv_module is None:
+ raise ValueError("No PV module with the provided model name exists in the catalogue")
+ for energy_system in self.building.energy_systems:
+ for idx, generation_system in enumerate(energy_system.generation_systems):
+ if generation_system.system_type == cte.PHOTOVOLTAIC:
+ new_system = selected_pv_module
+ # Preserve attributes that exist in the original but not in the new system
+ for attr in dir(generation_system):
+ # Skip private attributes and methods
+ if not attr.startswith('__') and not callable(getattr(generation_system, attr)):
+ if not hasattr(new_system, attr):
+ setattr(new_system, attr, getattr(generation_system, attr))
+ # Replace the old generation system with the new one
+ energy_system.generation_systems[idx] = new_system
+
+ def grid_tied_system(self):
+ if self.electricity_demand is not None:
+ electricity_demand = self.electricity_demand
+ else:
+ electricity_demand = [demand / cte.WATTS_HOUR_TO_JULES for demand in
+ HourlyElectricityDemand(self.building).calculate()]
+ rooftop_pv_output = [0] * 8760
+ facade_pv_output = [0] * 8760
+ rooftop_number_of_panels = 0
+ if 'rooftop' in self.pv_installation_type.lower():
+ np, ns = self.rooftop_sizing()
+ if self.simulation_model_type == 'explicit':
+ rooftop_number_of_panels = np * ns
+ rooftop_pv_output = self.explicit_model(pv_system=self.pv_system,
+ inverter_efficiency=self.inverter_efficiency,
+ number_of_panels=rooftop_number_of_panels,
+ irradiance=self.building.roofs[0].global_irradiance_tilted[
+ cte.HOUR],
+ outdoor_temperature=self.building.external_temperature[
+ cte.HOUR])
+
+ total_hourly_pv_output = [rooftop_pv_output[i] + facade_pv_output[i] for i in range(8760)]
+ imported_electricity = [0] * 8760
+ exported_electricity = [0] * 8760
+ for i in range(len(electricity_demand)):
+ transfer = total_hourly_pv_output[i] - electricity_demand[i]
+ if transfer > 0:
+ exported_electricity[i] = transfer
+ else:
+ imported_electricity[i] = abs(transfer)
+
+ results = {'building_name': self.building.name,
+ 'total_floor_area_m2': self.building.thermal_zones_from_internal_zones[0].total_floor_area,
+ 'roof_area_m2': self.building.roofs[0].perimeter_area, 'rooftop_panels': rooftop_number_of_panels,
+ 'rooftop_panels_area_m2': self.building.roofs[0].installed_solar_collector_area,
+ 'yearly_rooftop_ghi_kW/m2': self.building.roofs[0].global_irradiance[cte.YEAR][0] / 1000,
+ f'yearly_rooftop_tilted_radiation_{self.tilt_angle}_degree_kW/m2':
+ self.building.roofs[0].global_irradiance_tilted[cte.YEAR][0] / 1000,
+ 'yearly_rooftop_pv_production_kWh': sum(rooftop_pv_output) / 1000,
+ 'yearly_total_pv_production_kWh': sum(total_hourly_pv_output) / 1000,
+ 'specific_pv_production_kWh/kWp': sum(rooftop_pv_output) / (
+ float(self.pv_system.standard_test_condition_maximum_power) * rooftop_number_of_panels),
+ 'hourly_rooftop_poa_irradiance_W/m2': self.building.roofs[0].global_irradiance_tilted[cte.HOUR],
+ 'hourly_rooftop_pv_output_W': rooftop_pv_output, 'T_out': self.building.external_temperature[cte.HOUR],
+ 'building_electricity_demand_W': electricity_demand,
+ 'total_hourly_pv_system_output_W': total_hourly_pv_output, 'import_from_grid_W': imported_electricity,
+ 'export_to_grid_W': exported_electricity}
+ return results
+
+ def enrich(self):
+ system_archetype_name = self.building.energy_systems_archetype_name
+ archetype_name = '_'.join(system_archetype_name.lower().split())
+ if 'grid_tied' in archetype_name:
+ self.results = self.grid_tied_system()
+ hourly_pv_output = self.results['total_hourly_pv_system_output_W']
+ self.building.onsite_electrical_production[cte.HOUR] = hourly_pv_output
+ self.building.onsite_electrical_production[cte.MONTH] = MonthlyValues.get_total_month(hourly_pv_output)
+ self.building.onsite_electrical_production[cte.YEAR] = [sum(hourly_pv_output)]
+ if self.csv_output:
+ self.save_to_csv(self.results, self.output_path, f'{self.building.name}_pv_system_analysis.csv')
+
+ @staticmethod
+ def save_to_csv(data, output_path, filename='rooftop_system_results.csv'):
+ # Separate keys based on whether their values are single values or lists
+ single_value_keys = [key for key, value in data.items() if not isinstance(value, list)]
+ list_value_keys = [key for key, value in data.items() if isinstance(value, list)]
+
+ # Check if all lists have the same length
+ list_lengths = [len(data[key]) for key in list_value_keys]
+ if not all(length == list_lengths[0] for length in list_lengths):
+ raise ValueError("All lists in the dictionary must have the same length")
+
+ # Get the length of list values (assuming all lists are of the same length, e.g., 8760 for hourly data)
+ num_rows = list_lengths[0] if list_value_keys else 1
+
+ # Open the CSV file for writing
+ with open(output_path / filename, mode='w', newline='') as csv_file:
+ writer = csv.writer(csv_file)
+ # Write single-value data as a header section
+ for key in single_value_keys:
+ writer.writerow([key, data[key]])
+ # Write an empty row for separation
+ writer.writerow([])
+ # Write the header for the list values
+ writer.writerow(list_value_keys)
+ # Write each row for the lists
+ for i in range(num_rows):
+ row = [data[key][i] for key in list_value_keys]
+ writer.writerow(row)
diff --git a/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/radiation_tilted.py b/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/radiation_tilted.py
deleted file mode 100644
index 31bd5636..00000000
--- a/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/radiation_tilted.py
+++ /dev/null
@@ -1,110 +0,0 @@
-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] = 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/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/solar_angles.py b/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/solar_angles.py
deleted file mode 100644
index 560bd27c..00000000
--- a/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/solar_angles.py
+++ /dev/null
@@ -1,145 +0,0 @@
-import math
-import pandas as pd
-from datetime import datetime
-from pathlib import Path
-
-
-class CitySolarAngles:
- def __init__(self, location_latitude, location_longitude, tilt_angle, surface_azimuth_angle,
- standard_meridian=-75):
- 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/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/solar_calculator.py b/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/solar_calculator.py
new file mode 100644
index 00000000..87e4336e
--- /dev/null
+++ b/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/solar_calculator.py
@@ -0,0 +1,221 @@
+import math
+import pandas as pd
+from datetime import datetime
+import hub.helpers.constants as cte
+from hub.helpers.monthly_values import MonthlyValues
+
+
+class SolarCalculator:
+ def __init__(self, city, tilt_angle, surface_azimuth_angle, standard_meridian=-75,
+ solar_constant=1366.1, maximum_clearness_index=1, min_cos_zenith=0.065, maximum_zenith_angle=87):
+ """
+ A class to calculate the solar angles and solar irradiance on a tilted surface in the City
+ :param city: An object from the City class -> City
+ :param tilt_angle: tilt angle of surface -> float
+ :param surface_azimuth_angle: The orientation of the surface. 0 is North -> float
+ :param standard_meridian: A standard meridian is the meridian whose mean solar time is the basis of the time of day
+ observed in a time zone -> float
+ :param solar_constant: The amount of energy received by a given area one astronomical unit away from the Sun. It is
+ constant and must not be changed
+ :param maximum_clearness_index: This is used to calculate the diffuse fraction of the solar irradiance -> float
+ :param min_cos_zenith: This is needed to avoid unrealistic values in tilted irradiance calculations -> float
+ :param maximum_zenith_angle: This is needed to avoid negative values in tilted irradiance calculations -> float
+ """
+ self.city = city
+ self.location_latitude = city.latitude
+ self.location_longitude = city.longitude
+ self.location_latitude_rad = math.radians(self.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 = (self.location_longitude - standard_meridian) * 4
+ 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
+ timezone_offset = int(-standard_meridian / 15)
+ self.timezone = f'Etc/GMT{"+" if timezone_offset < 0 else "-"}{abs(timezone_offset)}'
+ self.eot = []
+ self.ast = []
+ self.hour_angles = []
+ self.declinations = []
+ self.solar_altitudes = []
+ self.solar_azimuths = []
+ self.zeniths = []
+ self.incidents = []
+ self.i_on = []
+ self.i_oh = []
+ self.times = pd.date_range(start='2023-01-01', end='2023-12-31 23:00', freq='h', tz=self.timezone)
+ self.solar_angles = pd.DataFrame(index=self.times)
+ self.day_of_year = self.solar_angles.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) % 24 # Adjust hours to fit within 0–23 range
+ 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
+
+ def dni_extra(self, day_of_year, zenith_radian):
+ i_on = self.solar_constant * (1 + 0.033 * math.cos(math.radians(360 * day_of_year / 365)))
+ i_oh = i_on * max(math.cos(zenith_radian), self.min_cos_zenith)
+ self.i_on.append(i_on)
+ self.i_oh.append(i_oh)
+ return i_on, i_oh
+
+ def clearness_index(self, ghi, i_oh):
+ k_t = ghi / i_oh
+ k_t = max(0, k_t)
+ k_t = min(self.maximum_clearness_index, k_t)
+ return k_t
+
+ def diffuse_fraction(self, k_t, zenith):
+ if k_t <= 0.22:
+ fraction_diffuse = 1 - 0.09 * k_t
+ elif k_t <= 0.8:
+ fraction_diffuse = (0.9511 - 0.1604 * k_t + 4.388 * k_t ** 2 - 16.638 * k_t ** 3 + 12.336 * k_t ** 4)
+ else:
+ fraction_diffuse = 0.165
+ if zenith > self.maximum_zenith_angle:
+ fraction_diffuse = 1
+ return fraction_diffuse
+
+ def radiation_components_horizontal(self, ghi, fraction_diffuse, zenith):
+ diffuse_horizontal = ghi * fraction_diffuse
+ dni = (ghi - diffuse_horizontal) / math.cos(math.radians(zenith))
+ if zenith > self.maximum_zenith_angle or dni < 0:
+ dni = 0
+ return diffuse_horizontal, dni
+
+ def radiation_components_tilted(self, diffuse_horizontal, dni, incident_angle):
+ beam_tilted = dni * math.cos(math.radians(incident_angle))
+ beam_tilted = max(beam_tilted, 0)
+ diffuse_tilted = diffuse_horizontal * ((1 + math.cos(math.radians(self.tilt_angle))) / 2)
+ total_radiation_tilted = beam_tilted + diffuse_tilted
+ return total_radiation_tilted
+
+ def solar_angles_calculator(self, csv_output=False):
+ 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)
+ self.incident_angle(solar_altitude_radians, solar_azimuth_radians)
+ self.dni_extra(day_of_year=day_of_year, zenith_radian=zenith_radians)
+ self.solar_angles['DateTime'] = self.times
+ self.solar_angles['AST'] = self.ast
+ self.solar_angles['hour angle'] = self.hour_angles
+ self.solar_angles['eot'] = self.eot
+ self.solar_angles['declination angle'] = self.declinations
+ self.solar_angles['solar altitude'] = self.solar_altitudes
+ self.solar_angles['zenith'] = self.zeniths
+ self.solar_angles['solar azimuth'] = self.solar_azimuths
+ self.solar_angles['incident angle'] = self.incidents
+ self.solar_angles['extraterrestrial normal radiation (Wh/m2)'] = self.i_on
+ self.solar_angles['extraterrestrial radiation on horizontal (Wh/m2)'] = self.i_oh
+ if csv_output:
+ self.solar_angles.to_csv('solar_angles_new.csv')
+
+ def tilted_irradiance_calculator(self):
+ if self.solar_angles.empty:
+ self.solar_angles_calculator()
+ for building in self.city.buildings:
+ hourly_tilted_irradiance = []
+ roof_ghi = building.roofs[0].global_irradiance[cte.HOUR]
+ for i in range(len(roof_ghi)):
+ k_t = self.clearness_index(ghi=roof_ghi[i], i_oh=self.i_oh[i])
+ fraction_diffuse = self.diffuse_fraction(k_t, self.zeniths[i])
+ diffuse_horizontal, dni = self.radiation_components_horizontal(ghi=roof_ghi[i],
+ fraction_diffuse=fraction_diffuse,
+ zenith=self.zeniths[i])
+ hourly_tilted_irradiance.append(int(self.radiation_components_tilted(diffuse_horizontal=diffuse_horizontal,
+ dni=dni,
+ incident_angle=self.incidents[i])))
+
+ building.roofs[0].global_irradiance_tilted[cte.HOUR] = hourly_tilted_irradiance
+ building.roofs[0].global_irradiance_tilted[cte.MONTH] = (MonthlyValues.get_total_month(
+ building.roofs[0].global_irradiance_tilted[cte.HOUR]))
+ building.roofs[0].global_irradiance_tilted[cte.YEAR] = [sum(building.roofs[0].global_irradiance_tilted[cte.MONTH])]
+
+
+
+
+
diff --git a/energy_system_modelling_package/random_assignation.py b/energy_system_modelling_package/random_assignation.py
index 390a0948..16cb7745 100644
--- a/energy_system_modelling_package/random_assignation.py
+++ b/energy_system_modelling_package/random_assignation.py
@@ -29,19 +29,41 @@ residential_systems_percentage = {'system 1 gas': 15,
'system 8 electricity': 35}
residential_new_systems_percentage = {
- 'Central 4 Pipes Air to Water Heat Pump and Gas Boiler with Independent Water Heating and PV': 100,
- 'Central 4 Pipes Air to Water Heat Pump and electrical Boiler with Independent Water Heating and PV': 0,
- 'Central 4 Pipes Ground to Water Heat Pump and Gas Boiler with Independent Water Heating and PV': 0,
- 'Central 4 Pipes Ground to Water Heat Pump and electrical Boiler with Independent Water Heating and PV': 0,
- 'Central 4 Pipes Water to Water Heat Pump and Gas Boiler with Independent Water Heating and PV': 0,
- 'Central 4 Pipes Water to Water Heat Pump and electrical Boiler with Independent Water Heating and PV': 0,
- 'Central 4 Pipes Air to Water Heat Pump and Gas Boiler with Independent Water Heating': 0,
- 'Central 4 Pipes Air to Water Heat Pump and electrical Boiler with Independent Water Heating': 0,
- 'Central 4 Pipes Ground to Water Heat Pump and Gas Boiler with Independent Water Heating': 0,
- 'Central 4 Pipes Ground to Water Heat Pump and electrical Boiler with Independent Water Heating': 0,
- 'Central 4 Pipes Water to Water Heat Pump and Gas Boiler with Independent Water Heating': 0,
- 'Central 4 Pipes Water to Water Heat Pump and electrical Boiler with Independent Water Heating': 0,
- 'Rooftop PV System': 0
+ 'Central Hydronic Air and Gas Source Heating System with Unitary Split Cooling and Air Source HP DHW and Grid Tied PV': 100,
+ 'Central Hydronic Air and Electricity Source Heating System with Unitary Split Cooling and Air Source HP DHW and Grid Tied PV': 0,
+ 'Central Hydronic Ground and Gas Source Heating System with Unitary Split Cooling and Air Source HP DHW and Grid Tied PV': 0,
+ 'Central Hydronic Ground and Electricity Source Heating System with Unitary Split Cooling and Air Source HP DHW '
+ 'and Grid Tied PV': 0,
+ 'Central Hydronic Water and Gas Source Heating System with Unitary Split Cooling and Air Source HP DHW and Grid Tied PV': 0,
+ 'Central Hydronic Water and Electricity Source Heating System with Unitary Split Cooling and Air Source HP DHW '
+ 'and Grid Tied PV': 0,
+ 'Central Hydronic Air and Gas Source Heating System with Unitary Split and Air Source HP DHW': 0,
+ 'Central Hydronic Air and Electricity Source Heating System with Unitary Split and Air Source HP DHW': 0,
+ 'Central Hydronic Ground and Gas Source Heating System with Unitary Split and Air Source HP DHW': 0,
+ 'Central Hydronic Ground and Electricity Source Heating System with Unitary Split and Air Source HP DHW': 0,
+ 'Central Hydronic Water and Gas Source Heating System with Unitary Split and Air Source HP DHW': 0,
+ 'Central Hydronic Water and Electricity Source Heating System with Unitary Split and Air Source HP DHW': 0,
+ 'Grid Tied PV System': 0,
+ 'system 1 gas': 0,
+ 'system 1 gas grid tied pv': 0,
+ 'system 1 electricity': 0,
+ 'system 1 electricity grid tied pv': 0,
+ 'system 2 gas': 0,
+ 'system 2 gas grid tied pv': 0,
+ 'system 2 electricity': 0,
+ 'system 2 electricity grid tied pv': 0,
+ 'system 3 and 4 gas': 0,
+ 'system 3 and 4 gas grid tied pv': 0,
+ 'system 3 and 4 electricity': 0,
+ 'system 3 and 4 electricity grid tied pv': 0,
+ 'system 6 gas': 0,
+ 'system 6 gas grid tied pv': 0,
+ 'system 6 electricity': 0,
+ 'system 6 electricity grid tied pv': 0,
+ 'system 8 gas': 0,
+ 'system 8 gas grid tied pv': 0,
+ 'system 8 electricity': 0,
+ 'system 8 electricity grid tied pv': 0,
}
non_residential_systems_percentage = {'system 1 gas': 0,
@@ -118,4 +140,3 @@ def call_random(_buildings: [Building], _systems_percentage):
_buildings[_selected_buildings[_position]].energy_systems_archetype_name = case['system']
_position += 1
return _buildings
-
diff --git a/energy_system_retrofit.py b/energy_system_retrofit.py
index 22c5586b..94f10a23 100644
--- a/energy_system_retrofit.py
+++ b/energy_system_retrofit.py
@@ -3,8 +3,10 @@ import subprocess
from building_modelling.ep_run_enrich import energy_plus_workflow
from energy_system_modelling_package.energy_system_modelling_factories.montreal_energy_system_archetype_modelling_factory import \
MontrealEnergySystemArchetypesSimulationFactory
-from energy_system_modelling_package.energy_system_modelling_factories.pv_assessment.pv_feasibility import \
- pv_feasibility
+from energy_system_modelling_package.energy_system_modelling_factories.pv_assessment.pv_system_assessment import \
+ PvSystemAssessment
+from energy_system_modelling_package.energy_system_modelling_factories.pv_assessment.solar_calculator import \
+ SolarCalculator
from hub.imports.geometry_factory import GeometryFactory
from hub.helpers.dictionaries import Dictionaries
from hub.imports.construction_factory import ConstructionFactory
@@ -22,9 +24,10 @@ from costing_package.constants import SYSTEM_RETROFIT_AND_PV, CURRENT_STATUS
from hub.exports.exports_factory import ExportsFactory
# Specify the GeoJSON file path
+main_path = Path(__file__).parent.resolve()
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 = process_geojson(x=-73.5681295982132, y=45.49218262677643, diff=0.00006, path=main_path)
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)
@@ -34,6 +37,8 @@ simulation_results_path = (Path(__file__).parent / 'out_files' / 'simulation_res
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)
+pv_assessment_path = output_path / 'pv_outputs'
+pv_assessment_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',
@@ -49,7 +54,6 @@ 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)
random_assignation.call_random(city.buildings, random_assignation.residential_systems_percentage)
EnergySystemsFactory('montreal_custom', city).enrich()
@@ -65,12 +69,35 @@ for building in city.buildings:
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()
-EnergySystemsSizingFactory('pv_sizing', city).enrich()
EnergySystemsSizingFactory('peak_load_sizing', city).enrich()
+# # Initialize solar calculation parameters (e.g., azimuth, altitude) and compute irradiance and solar angles
+tilt_angle = 37
+solar_parameters = SolarCalculator(city=city,
+ surface_azimuth_angle=180,
+ tilt_angle=tilt_angle,
+ standard_meridian=-75)
+solar_angles = solar_parameters.solar_angles # Obtain solar angles for further analysis
+solar_parameters.tilted_irradiance_calculator() # Calculate the solar radiation on a tilted surface
for building in city.buildings:
MontrealEnergySystemArchetypesSimulationFactory(f'archetype_cluster_{building.energy_systems_archetype_cluster_id}',
building,
simulation_results_path).enrich()
+ if 'PV' in building.energy_systems_archetype_name:
+ PvSystemAssessment(building=building,
+ pv_system=None,
+ battery=None,
+ electricity_demand=None,
+ tilt_angle=tilt_angle,
+ solar_angles=solar_angles,
+ pv_installation_type='rooftop',
+ simulation_model_type='explicit',
+ module_model_name=None,
+ inverter_efficiency=0.95,
+ system_catalogue_handler=None,
+ roof_percentage_coverage=0.75,
+ facade_coverage_percentage=0,
+ csv_output=False,
+ output_path=pv_assessment_path).enrich()
retrofitted_energy_consumption = consumption_data(city)
retrofitted_life_cycle_cost = {}
for building in city.buildings:
diff --git a/example_codes/pv_potential_assessment.py b/example_codes/pv_potential_assessment.py
new file mode 100644
index 00000000..e69de29b
diff --git a/example_codes/pv_system_assessment.py b/example_codes/pv_system_assessment.py
new file mode 100644
index 00000000..8c10642e
--- /dev/null
+++ b/example_codes/pv_system_assessment.py
@@ -0,0 +1,86 @@
+from pathlib import Path
+import subprocess
+from building_modelling.ep_run_enrich import energy_plus_workflow
+from energy_system_modelling_package import random_assignation
+from energy_system_modelling_package.energy_system_modelling_factories.pv_assessment.pv_system_assessment import \
+ PvSystemAssessment
+from energy_system_modelling_package.energy_system_modelling_factories.pv_assessment.solar_calculator import \
+ SolarCalculator
+from hub.imports.energy_systems_factory import EnergySystemsFactory
+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 building_modelling.geojson_creator import process_geojson
+from hub.exports.exports_factory import ExportsFactory
+import hub.helpers.constants as cte
+# Define paths for input and output directories, ensuring directories are created if they do not exist
+main_path = Path(__file__).parent.parent.resolve()
+input_files_path = (Path(__file__).parent.parent / 'input_files')
+input_files_path.mkdir(parents=True, exist_ok=True)
+output_path = (Path(__file__).parent.parent / 'out_files').resolve()
+output_path.mkdir(parents=True, exist_ok=True)
+# Define specific paths for outputs from EnergyPlus and SRA (Simplified Radiosity Algorith) and PV calculation processes
+energy_plus_output_path = output_path / 'energy_plus_outputs'
+energy_plus_output_path.mkdir(parents=True, exist_ok=True)
+sra_output_path = output_path / 'sra_outputs'
+sra_output_path.mkdir(parents=True, exist_ok=True)
+pv_assessment_path = output_path / 'pv_outputs'
+pv_assessment_path.mkdir(parents=True, exist_ok=True)
+# Generate a GeoJSON file for city buildings based on latitude, longitude, and building dimensions
+geojson_file = process_geojson(x=-73.5681295982132, y=45.49218262677643, path=main_path, diff=0.0001)
+geojson_file_path = input_files_path / 'output_buildings.geojson'
+# Initialize a city object from the geojson file, mapping building functions using a predefined dictionary
+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 with construction, usage, and weather information specific to the location
+ConstructionFactory('nrcan', city).enrich()
+UsageFactory('nrcan', city).enrich()
+WeatherFactory('epw', city).enrich()
+# Execute the EnergyPlus workflow to simulate building energy performance and generate output
+# energy_plus_workflow(city, energy_plus_output_path)
+# Export the city data in SRA-compatible format to facilitate solar radiation assessment
+ExportsFactory('sra', city, sra_output_path).export()
+# Run SRA simulation using an external command, passing the generated SRA XML file path as input
+sra_path = (sra_output_path / f'{city.name}_sra.xml').resolve()
+subprocess.run(['sra', str(sra_path)])
+# Enrich city data with SRA simulation results for subsequent analysis
+ResultFactory('sra', city, sra_output_path).enrich()
+# Assign PV system archetype name to the buildings in city
+random_assignation.call_random(city.buildings, random_assignation.residential_new_systems_percentage)
+# Enrich city model with Montreal future systems parameters
+EnergySystemsFactory('montreal_future', city).enrich()
+# # Initialize solar calculation parameters (e.g., azimuth, altitude) and compute irradiance and solar angles
+tilt_angle = 37
+solar_parameters = SolarCalculator(city=city,
+ surface_azimuth_angle=180,
+ tilt_angle=tilt_angle,
+ standard_meridian=-75)
+solar_angles = solar_parameters.solar_angles # Obtain solar angles for further analysis
+solar_parameters.tilted_irradiance_calculator() # Calculate the solar radiation on a tilted surface
+# # PV modelling building by building
+#List of available PV modules ['RE400CAA Pure 2', 'RE410CAA Pure 2', 'RE420CAA Pure 2', 'RE430CAA Pure 2',
+# 'REC600AA Pro M', 'REC610AA Pro M', 'REC620AA Pro M', 'REC630AA Pro M', 'REC640AA Pro M']
+for building in city.buildings:
+ PvSystemAssessment(building=building,
+ pv_system=None,
+ battery=None,
+ tilt_angle=tilt_angle,
+ solar_angles=solar_angles,
+ pv_installation_type='rooftop',
+ simulation_model_type='explicit',
+ module_model_name='REC640AA Pro M',
+ inverter_efficiency=0.95,
+ system_catalogue_handler='montreal_future',
+ roof_percentage_coverage=0.75,
+ facade_coverage_percentage=0,
+ csv_output=False,
+ output_path=pv_assessment_path).enrich()
+
+
diff --git a/hub/catalog_factories/data_models/energy_systems/thermal_storage_system.py b/hub/catalog_factories/data_models/energy_systems/thermal_storage_system.py
index ca773e09..583345aa 100644
--- a/hub/catalog_factories/data_models/energy_systems/thermal_storage_system.py
+++ b/hub/catalog_factories/data_models/energy_systems/thermal_storage_system.py
@@ -119,7 +119,7 @@ class ThermalStorageSystem(EnergyStorageSystem):
'height [m]': self.height,
'layers': _layers,
'maximum operating temperature [Celsius]': self.maximum_operating_temperature,
- 'storage_medium': self.storage_medium.to_dictionary(),
+ 'storage_medium': _medias,
'heating coil capacity [W]': self.heating_coil_capacity
}
}
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 4a9672ad..6c5678f0 100644
--- a/hub/catalog_factories/energy_systems/montreal_future_system_catalogue.py
+++ b/hub/catalog_factories/energy_systems/montreal_future_system_catalogue.py
@@ -30,7 +30,8 @@ class MontrealFutureSystemCatalogue(Catalog):
path = str(path / 'montreal_future_systems.xml')
with open(path, 'r', encoding='utf-8') as xml:
self._archetypes = xmltodict.parse(xml.read(),
- force_list=['pv_generation_component', 'templateStorages', 'demand'])
+ force_list=['pv_generation_component', 'templateStorages', 'demand',
+ 'system', 'system_id'])
self._storage_components = self._load_storage_components()
self._generation_components = self._load_generation_components()
@@ -49,7 +50,7 @@ class MontrealFutureSystemCatalogue(Catalog):
'non_pv_generation_component']
if non_pv_generation_components is not None:
for non_pv in non_pv_generation_components:
- system_id = non_pv['system_id']
+ system_id = non_pv['generation_system_id']
name = non_pv['name']
system_type = non_pv['system_type']
model_name = non_pv['model_name']
@@ -181,7 +182,7 @@ class MontrealFutureSystemCatalogue(Catalog):
'pv_generation_component']
if pv_generation_components is not None:
for pv in pv_generation_components:
- system_id = pv['system_id']
+ system_id = pv['generation_system_id']
name = pv['name']
system_type = pv['system_type']
model_name = pv['model_name']
diff --git a/hub/city_model_structure/building.py b/hub/city_model_structure/building.py
index c5fb36c8..51b53424 100644
--- a/hub/city_model_structure/building.py
+++ b/hub/city_model_structure/building.py
@@ -840,53 +840,55 @@ class Building(CityObject):
Get energy consumption of different sectors
return: dict
"""
- fuel_breakdown = {cte.ELECTRICITY: {cte.LIGHTING: self.lighting_electrical_demand[cte.YEAR][0],
- cte.APPLIANCES: self.appliances_electrical_demand[cte.YEAR][0]}}
+ fuel_breakdown = {cte.ELECTRICITY: {cte.LIGHTING: self.lighting_electrical_demand[cte.YEAR][0] if self.lighting_electrical_demand else 0,
+ cte.APPLIANCES: self.appliances_electrical_demand[cte.YEAR][0] if self.appliances_electrical_demand else 0}}
energy_systems = self.energy_systems
- for energy_system in energy_systems:
- demand_types = energy_system.demand_types
- generation_systems = energy_system.generation_systems
- for demand_type in demand_types:
- for generation_system in generation_systems:
- if generation_system.system_type != cte.PHOTOVOLTAIC:
- if generation_system.fuel_type not in fuel_breakdown:
- fuel_breakdown[generation_system.fuel_type] = {}
- 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_capacity is not None:
- fuel_breakdown[cte.ELECTRICITY][f'{demand_type}'] += storage_system.heating_coil_energy_consumption[f'{demand_type}'][cte.YEAR][0]
- #TODO: When simulation models of all energy system archetypes are created, this part can be removed
- 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 key == cte.ELECTRICITY and cte.COOLING not in fuel_breakdown[key]:
- 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]:
+ if energy_systems is not None:
+ for energy_system in energy_systems:
+ demand_types = energy_system.demand_types
+ generation_systems = energy_system.generation_systems
+ for demand_type in demand_types:
+ for generation_system in generation_systems:
+ if generation_system.system_type != cte.PHOTOVOLTAIC:
+ if generation_system.fuel_type not in fuel_breakdown:
+ fuel_breakdown[generation_system.fuel_type] = {}
+ 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)[f'{demand_type}'][cte.YEAR][0]
+ #TODO: When simulation models of all energy system archetypes are created, this part can be removed
+ 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 key == cte.ELECTRICITY and cte.COOLING not in fuel_breakdown[key]:
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]
+ if cte.COOLING in energy_system.demand_types and cte.COOLING not in fuel_breakdown[key]:
+ if self.cooling_consumption:
+ fuel_breakdown[energy_system.generation_systems[0].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:
+ if self.heating_consumption:
+ fuel_breakdown[energy_system.generation_systems[0].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:
+ if self.domestic_hot_water_consumption:
+ fuel_breakdown[energy_system.generation_systems[0].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/data/energy_systems/montreal_future_systems.xml b/hub/data/energy_systems/montreal_future_systems.xml
index 583a004f..12f5130e 100644
--- a/hub/data/energy_systems/montreal_future_systems.xml
+++ b/hub/data/energy_systems/montreal_future_systems.xml
@@ -17,7 +17,7 @@
- 1
+ 1
Natural-Gas Boiler
boiler
ALP080B
@@ -56,7 +56,7 @@
False
- 2
+ 2
Natural-Gas boiler
boiler
ALP105B
@@ -95,7 +95,7 @@
False
- 3
+ 3
Natural-Gas boiler
boiler
ALP150B
@@ -134,7 +134,7 @@
False
- 4
+ 4
Natural-Gas boiler
boiler
ALP210B
@@ -173,7 +173,7 @@
False
- 5
+ 5
Natural-Gas boiler
boiler
ALTAC-136
@@ -212,7 +212,7 @@
False
- 6
+ 6
Natural-Gas boiler
boiler
ALTA-120
@@ -251,7 +251,7 @@
False
- 7
+ 7
Natural-Gas boiler
boiler
ASPN-085
@@ -290,7 +290,7 @@
False
- 8
+ 8
Natural-Gas boiler
boiler
ASPN-110
@@ -329,7 +329,7 @@
False
- 9
+ 9
Natural-Gas boiler
boiler
ASPNC-155
@@ -368,7 +368,7 @@
False
- 10
+ 10
Natural-Gas boiler
boiler
K2WTC-135B
@@ -407,7 +407,7 @@
False
- 11
+ 11
Natural-Gas boiler
boiler
K2WTC-180B
@@ -446,27 +446,27 @@
False
- 12
+ 12
Photovoltaic Module
photovoltaic
445MS
Canadian Solar
-
-
-
-
-
-
-
-
-
+ 332
+ 0.201
+ 20
+ 40
+ 800
+ 25
+ 1000
+ 445
+ 0.35
2.01
1.048
- 13
+ 13
Air-to-Water heat pump
heat pump
CMAA 012
@@ -511,7 +511,7 @@
False
- 14
+ 14
Air-to-Water heat pump
heat pump
CMAA 70
@@ -556,7 +556,7 @@
False
- 15
+ 15
Air-to-Water heat pump
heat pump
CMAA 140
@@ -601,7 +601,7 @@
False
- 16
+ 16
template Natural-Gas boiler
boiler
@@ -642,7 +642,7 @@
False
- 17
+ 17
template Electric boiler
boiler
@@ -683,15 +683,15 @@
False
- 18
- template Air-to-Water heat pump with storage
+ 18
+ template reversible 4-pipe air-to-water heat pump with storage
heat pump
- 2
+ 2.5
True
electricity
Air
@@ -736,8 +736,8 @@
True
- 19
- template Groundwater-to-Water heat pump with storage
+ 19
+ template reversible 4-pipe groundwater-to-water heat pump with storage
heat pump
@@ -777,8 +777,8 @@
True
- 20
- template Water-to-Water heat pump with storage
+ 20
+ template reversible 4-pipe water-to-water heat pump with storage
heat pump
@@ -818,7 +818,7 @@
False
- 21
+ 21
template Natural-Gas boiler
boiler
@@ -857,7 +857,7 @@
False
- 22
+ 22
template Electric boiler
boiler
@@ -896,8 +896,8 @@
False
- 23
- template Air-to-Water heat pump
+ 23
+ template reversible 4-pipe air-to-water heat pump
heat pump
@@ -912,7 +912,7 @@
- 4
+ 4.5
@@ -947,8 +947,8 @@
True
- 24
- template Groundwater-to-Water heat pump
+ 24
+ template reversible 4-pipe groundwater-to-water heat pump
heat pump
@@ -986,8 +986,8 @@
True
- 25
- template Water-to-Water heat pump
+ 25
+ template reversible 4-pipe water-to-water heat pump
heat pump
@@ -1024,29 +1024,440 @@
True
-
- 26
- template Photovoltaic Module
- photovoltaic
+
+ 26
+ template reversible 2-pipe air-to-water heat pump with storage
+ heat pump
+
+
+
+ 3
+ True
+ electricity
+ Air
+ Water
+
+
+
+ 4.5
+
+
+
- 0.2
-
-
-
-
-
-
-
- 2.0
- 1.0
+
+
+
+
+
+
+
+ bi-quadratic
+ COP
+ source_temperature
+ supply_temperature
+
+
+
+
+
+ bi-quadratic
+ COP
+ source_temperature
+ supply_temperature
+
+
+
+
+ 6
+
+ False
+
+
+ False
+
+
+ 27
+ template reversible 2-pipe groundwater-to-water heat pump with storage
+ heat pump
+
+
+
+
+
+ 3.5
+ True
+ electricity
+ Ground
+ Water
+
+
+
+ 5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 6
+
+ False
+
+
+ False
+
+
+ 28
+ template reversible 2-pipe water-to-water heat pump with storage
+ heat pump
+
+
+
+
+
+ 4
+ True
+ electricity
+ Water
+ Water
+
+
+
+ 6
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 6
+
+ False
+
+
+ False
+
+
+ 29
+ template reversible 2-pipe air-to-water heat pump
+ heat pump
+
+
+
+
+
+ 3
+ True
+ electricity
+ Air
+ Water
+
+
+
+ 4.5
+
+
+
+
+
+
+
+
+
+
+
+ bi-quadratic
+ COP
+ source_temperature
+ supply_temperature
+
+
+
+
+
+ bi-quadratic
+ COP
+ source_temperature
+ supply_temperature
+
+
+ False
+
+
False
-
+
- 27
+ 30
+ template reversible 2-pipe groundwater-to-water heat pump
+ heat pump
+
+
+
+
+
+ 3.5
+ True
+ electricity
+ Ground
+ Water
+
+
+
+ 5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ False
+
+
+ False
+
+
+ 31
+ template reversible 2-pipe water-to-water heat pump
+ heat pump
+
+
+
+
+
+ 4
+ True
+ electricity
+ Water
+ Water
+
+
+
+ 6
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ False
+
+
+ False
+
+
+ 32
+ template air-to-water heating heat pump
+ heat pump
+
+
+
+
+
+ 3
+ False
+ electricity
+ Air
+ Water
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ bi-quadratic
+ COP
+ source_temperature
+ supply_temperature
+
+
+
+
+
+
+
+ False
+
+
+ False
+
+
+ 33
+ template groundwater-to-water heating heat pump
+ heat pump
+
+
+
+
+
+ 3.5
+ False
+ electricity
+ Ground
+ Water
+
+
+
+ 5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ False
+
+
+ False
+
+
+ 34
+ template water-to-water heating heat pump
+ heat pump
+
+
+
+
+
+ 4
+ False
+ electricity
+ Water
+ Water
+
+
+
+ 6
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ False
+
+
+ False
+
+
+ 35
+ template unitary split system
+ heat pump
+
+
+
+
+
+
+ False
+ electricity
+ Air
+ Air
+
+
+
+ 3
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ bi-quadratic
+ COP
+ source_temperature
+ supply_temperature
+
+
+
+
+ False
+
+
+ False
+
+
+ 36
template domestic hot water heat pump
heat pump
@@ -1092,6 +1503,333 @@
False
+
+ 37
+ template gas furnace
+ furnace
+
+
+
+
+
+ 0.85
+
+ natural gas
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ False
+
+
+ 38
+ template electrical furnace
+ furnace
+
+
+
+
+
+ 0.85
+
+ electricity
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ False
+
+
+ 39
+ template air cooled DX with external condenser
+ cooler
+
+
+
+
+
+
+
+ electricity
+
+
+
+
+
+ 3.23
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ False
+
+
+ 40
+ template Photovoltaic Module
+ photovoltaic
+
+
+
+ 0.2
+ 20
+ 45
+ 800
+ 25
+ 1000
+ 500
+ 0.34
+ 2.0
+ 1.0
+
+
+ False
+
+
+ 41
+ Photovoltaic Module
+ photovoltaic
+ RE400CAA Pure 2
+ REC
+ 305
+ 0.206
+ 20
+ 44
+ 800
+ 25
+ 1000
+ 400
+ 0.24
+ 1.86
+ 1.04
+
+
+ False
+
+
+ 42
+ Photovoltaic Module
+ photovoltaic
+ RE410CAA Pure 2
+ REC
+ 312
+ 0.211
+ 20
+ 44
+ 800
+ 25
+ 1000
+ 410
+ 0.24
+ 1.86
+ 1.04
+
+
+ False
+
+
+ 43
+ Photovoltaic Module
+ photovoltaic
+ RE420CAA Pure 2
+ REC
+ 320
+ 0.217
+ 20
+ 44
+ 800
+ 25
+ 1000
+ 420
+ 0.24
+ 1.86
+ 1.04
+
+
+ False
+
+
+ 44
+ Photovoltaic Module
+ photovoltaic
+ RE430CAA Pure 2
+ REC
+ 327
+ 0.222
+ 20
+ 44
+ 800
+ 25
+ 1000
+ 430
+ 0.24
+ 1.86
+ 1.04
+
+
+ False
+
+
+ 45
+ Photovoltaic Module
+ photovoltaic
+ REC600AA Pro M
+ REC
+ 457
+ 0.211
+ 20
+ 44
+ 800
+ 25
+ 1000
+ 600
+ 0.24
+ 2.17
+ 1.3
+
+
+ False
+
+
+ 46
+ Photovoltaic Module
+ photovoltaic
+ REC610AA Pro M
+ REC
+ 464
+ 0.215
+ 20
+ 44
+ 800
+ 25
+ 1000
+ 610
+ 0.24
+ 2.17
+ 1.3
+
+
+ False
+
+
+ 47
+ Photovoltaic Module
+ photovoltaic
+ REC620AA Pro M
+ REC
+ 472
+ 0.218
+ 20
+ 44
+ 800
+ 25
+ 1000
+ 620
+ 0.24
+ 2.17
+ 1.3
+
+
+ False
+
+
+ 48
+ Photovoltaic Module
+ photovoltaic
+ REC630AA Pro M
+ REC
+ 480
+ 0.222
+ 20
+ 44
+ 800
+ 25
+ 1000
+ 630
+ 0.24
+ 2.17
+ 1.3
+
+
+ False
+
+
+ 49
+ Photovoltaic Module
+ photovoltaic
+ REC640AA Pro M
+ REC
+ 487
+ 0.215
+ 20
+ 44
+ 800
+ 25
+ 1000
+ 640
+ 0.24
+ 2.17
+ 1.3
+
+
+ False
+
@@ -1274,7 +2012,7 @@
sensible
- 5000
+ 0
@@ -1318,7 +2056,7 @@
electricity
- 26
+ 40
@@ -1401,6 +2139,115 @@
8
+ 4 pipe central air to water heat pump with storage tank
+ schemas/ASHP+TES+GasBoiler.jpg
+
+ heating
+ cooling
+
+
+ 18
+
+
+
+ 9
+ 4 pipe central ground to water heat pump with storage tank
+ schemas/ASHP+TES+GasBoiler.jpg
+
+ heating
+ cooling
+
+
+ 19
+
+
+
+ 10
+ 4 pipe central water to water heat pump with storage tank
+ schemas/ASHP+TES+GasBoiler.jpg
+
+ heating
+ cooling
+
+
+ 20
+
+
+
+ 11
+ hydronic heating system with air source heat pump storage tank and auxiliary gas boiler
+ schemas/ASHP+TES+GasBoiler.jpg
+
+ heating
+
+
+ 32
+ 16
+
+
+
+ 12
+ hydronic heating system with air source heat pump storage tank and auxiliary electric boiler
+ schemas/ASHP+TES+GasBoiler.jpg
+
+ heating
+
+
+ 32
+ 17
+
+
+
+ 13
+ hydronic heating system with ground source heat pump storage tank and auxiliary gas boiler
+ schemas/ASHP+TES+GasBoiler.jpg
+
+ heating
+
+
+ 33
+ 16
+
+
+
+ 14
+ hydronic heating system with ground source heat pump storage tank and auxiliary electric boiler
+ schemas/ASHP+TES+GasBoiler.jpg
+
+ heating
+
+
+ 33
+ 17
+
+
+
+ 15
+ hydronic heating system with water source heat pump storage tank and auxiliary gas boiler
+ schemas/ASHP+TES+GasBoiler.jpg
+
+ heating
+
+
+ 34
+ 16
+
+
+
+ 16
+ hydronic heating system with water source heat pump storage tank and auxiliary gas boiler
+ schemas/ASHP+TES+GasBoiler.jpg
+
+ heating
+ cooling
+
+
+ 35
+ 17
+
+
+
+ 17
district heating network with air to water heat pump gas boiler thermal storage tank
schemas/ASHP+TES+GasBoiler.jpg
@@ -1412,7 +2259,7 @@
- 9
+ 18
district heating network with air to water heat pump electrical boiler thermal storage tank
schemas/ASHP+TES+GasBoiler.jpg
@@ -1424,7 +2271,7 @@
- 10
+ 19
district heating network with ground to water heat pump gas boiler thermal storage tank
schemas/ASHP+TES+GasBoiler.jpg
@@ -1436,7 +2283,7 @@
- 11
+ 20
district heating network with ground to water heat pump electrical boiler thermal storage tank
schemas/ASHP+TES+GasBoiler.jpg
@@ -1448,7 +2295,7 @@
- 12
+ 21
district heating network with water to water heat pump gas boiler thermal storage tank
schemas/ASHP+TES+GasBoiler.jpg
@@ -1460,7 +2307,7 @@
- 13
+ 22
district heating network with water to water heat pump electrical boiler thermal storage tank
schemas/ASHP+TES+GasBoiler.jpg
@@ -1472,148 +2319,467 @@
- 14
- Unitary air to water heat pump cooling system
+ 23
+ Unitary split cooling system
schemas/ASHP+TES+GasBoiler.jpg
cooling
- 23
+ 35
- 15
- Unitary ground to water heat pump cooling system
- schemas/ASHP+TES+GasBoiler.jpg
-
- cooling
-
-
- 24
-
-
-
- 16
- unitary water to water heat pump cooling system
- schemas/ASHP+TES+GasBoiler.jpg
-
- cooling
-
-
- 25
-
-
-
- 17
+ 24
Domestic Hot Water Heat Pump with Coiled Storage
schemas/ASHP+TES+GasBoiler.jpg
domestic_hot_water
- 27
+ 36
+
+ 25
+ Unitary air conditioner with baseboard heater fuel fired boiler
+ schemas/ASHP+TES+GasBoiler.jpg
+
+ heating
+ domestic_hot_water
+
+
+ 21
+
+
+
+ 26
+ Unitary air conditioner with baseboard heater electrical boiler
+ schemas/ASHP+TES+GasBoiler.jpg
+
+ heating
+ domestic_hot_water
+
+
+ 22
+
+
+
+ 27
+ 4 pipe fan coils with fuel fired boiler
+ schemas/ASHP+TES+GasBoiler.jpg
+
+ heating
+ domestic_hot_water
+
+
+ 21
+
+
+
+ 28
+ 4 pipe fan coils with electrical resistance water boiler
+ schemas/ASHP+TES+GasBoiler.jpg
+
+ heating
+ domestic_hot_water
+
+
+ 21
+
+
+
+ 29
+ Single zone packaged rooftop unit with fuel-fired furnace and baseboards and fuel boiler for acs
+ schemas/ASHP+TES+GasBoiler.jpg
+
+ heating
+ domestic_hot_water
+
+
+ 37
+
+
+
+ 30
+ Single zone packaged rooftop unit with electrical resistance furnace and baseboards and fuel boiler for acs
+ schemas/ASHP+TES+GasBoiler.jpg
+
+ heating
+ domestic_hot_water
+
+
+ 38
+
+
+
+ 31
+ Single zone make-up air unit with baseboard heating with fuel fired boiler
+ schemas/ASHP+TES+GasBoiler.jpg
+
+ heating
+ domestic_hot_water
+
+
+ 21
+
+
+
+ 32
+ Single zone make-up air unit with electrical baseboard heating and DHW with resistance
+ schemas/ASHP+TES+GasBoiler.jpg
+
+ heating
+ domestic_hot_water
+
+
+ 22
+
+
+
+ 33
+ Multi-zone built-up system with baseboard heater hydronic with fuel fired boiler
+ schemas/ASHP+TES+GasBoiler.jpg
+
+ heating
+ domestic_hot_water
+
+
+ 21
+
+
+
+ 34
+ Multi-zone built-up system with electrical baseboard heater and electrical hot water
+ schemas/ASHP+TES+GasBoiler.jpg
+
+ heating
+ domestic_hot_water
+
+
+ 22
+
+
+
+ 35
+ Unitary air conditioner air cooled DX with external condenser
+ schemas/ASHP+TES+GasBoiler.jpg
+
+ cooling
+
+
+ 39
+
+
+
+ 36
+ 4 pipe fan coils with water cooled, water chiller
+ schemas/ASHP+TES+GasBoiler.jpg
+
+ cooling
+
+
+ 39
+
+
+
+ 37
+ Single zone packaged rooftop unit with air cooled DX
+ schemas/ASHP+TES+GasBoiler.jpg
+
+ cooling
+
+
+ 39
+
+
+
+ 38
+ Single zone make-up air unit with air cooled DX
+ schemas/ASHP+TES+GasBoiler.jpg
+
+ cooling
+
+
+ 39
+
+
+
+ 39
+ Multi-zone built-up system with water cooled, water chiller
+ schemas/ASHP+TES+GasBoiler.jpg
+
+ cooling
+
+
+ 39
+
+
- Central 4 Pipes Air to Water Heat Pump and Gas Boiler with Independent Water Heating and PV
+ Central Hydronic Air and Gas Source Heating System with Unitary Split Cooling and Air Source HP DHW and Grid Tied PV
1
- 2
- 17
+ 11
+ 23
+ 24
- Central 4 Pipes Air to Water Heat Pump and electrical Boiler with Independent Water Heating and PV
+ Central Hydronic Air and Electricity Source Heating System with Unitary Split Cooling and Air Source HP DHW and Grid Tied PV
1
- 3
- 8
+ 12
+ 23
+ 24
- Central 4 Pipes Ground to Water Heat Pump and Gas Boiler with Independent Water Heating and PV
+ Central Hydronic Ground and Gas Source Heating System with Unitary Split Cooling and Air Source HP DHW and Grid Tied PV
1
- 4
- 17
+ 13
+ 23
+ 24
- Central 4 Pipes Ground to Water Heat Pump and electrical Boiler with Independent Water Heating and PV
+ Central Hydronic Ground and Electricity Source Heating System with Unitary Split Cooling and Air Source HP DHW and Grid Tied PV
1
- 5
- 17
+ 14
+ 23
+ 24
- Central 4 Pipes Water to Water Heat Pump and Gas Boiler with Independent Water Heating and PV
+ Central Hydronic Water and Gas Source Heating System with Unitary Split Cooling and Air Source HP DHW and Grid Tied PV
1
- 6
- 17
+ 15
+ 23
+ 24
- Central 4 Pipes Water to Water Heat Pump and electrical Boiler with Independent Water Heating and PV
+ Central Hydronic Water and Electricity Source Heating System with Unitary Split Cooling and Air Source HP DHW and Grid Tied PV
1
- 7
- 17
+ 16
+ 23
+ 24
- Central 4 Pipes Air to Water Heat Pump and Gas Boiler with Independent Water Heating
+ Central Hydronic Air and Gas Source Heating System with Unitary Split and Air Source HP DHW
- 2
- 17
+ 11
+ 23
+ 24
- Central 4 Pipes Air to Water Heat Pump and electrical Boiler with Independent Water Heating
+ Central Hydronic Air and Electricity Source Heating System with Unitary Split and Air Source HP DHW
- 3
- 17
+ 12
+ 23
+ 24
- Central 4 Pipes Ground to Water Heat Pump and Gas Boiler with Independent Water Heating
+ Central Hydronic Ground and Gas Source Heating System with Unitary Split and Air Source HP DHW
- 4
- 17
+ 13
+ 23
+ 24
- Central 4 Pipes Ground to Water Heat Pump and electrical Boiler with Independent Water Heating
+ Central Hydronic Ground and Electricity Source Heating System with Unitary Split and Air Source HP DHW
- 5
- 17
+ 14
+ 23
+ 24
- Central 4 Pipes Water to Water Heat Pump and Gas Boiler with Independent Water Heating
+ Central Hydronic Water and Gas Source Heating System with Unitary Split and Air Source HP DHW
- 6
- 17
+ 15
+ 23
+ 24
- Central 4 Pipes Water to Water Heat Pump and electrical Boiler with Independent Water Heating
+ Central Hydronic Water and Electricity Source Heating System with Unitary Split and Air Source HP DHW
- 7
- 17
+ 16
+ 23
+ 24
- Rooftop PV System
+ Grid Tied PV System
1
+
+ system 1 gas
+
+ 25
+ 35
+
+
+
+ system 1 gas grid tied pv
+
+ 1
+ 25
+ 35
+
+
+
+ system 1 electricity
+
+ 26
+ 35
+
+
+
+ system 1 electricity grid tied pv
+
+ 26
+ 1
+ 35
+
+
+
+ system 2 gas
+
+ 27
+ 36
+
+
+
+ system 2 gas grid tied pv
+
+ 1
+ 27
+ 36
+
+
+
+ system 2 electricity
+
+ 28
+ 36
+
+
+
+ system 2 electricity grid tied pv
+
+ 1
+ 28
+ 36
+
+
+
+ system 3 and 4 gas
+
+ 29
+ 37
+
+
+
+ system 3 and 4 gas grid tied pv
+
+ 1
+ 29
+ 37
+
+
+
+ system 3 and 4 electricity
+
+ 30
+ 37
+
+
+
+ system 3 and 4 electricity grid tied pv
+
+ 30
+ 37
+ 1
+
+
+
+ system 6 gas
+
+ 33
+ 39
+
+
+
+ system 6 gas grid tied pv
+
+ 33
+ 39
+ 1
+
+
+
+ system 6 electricity
+
+ 34
+ 39
+
+
+
+ system 6 electricity grid tied pv
+
+ 34
+ 39
+ 1
+
+
+
+ system 7 electricity grid tied pv
+
+ 1
+ 8
+ 24
+
+
+
+ system 8 gas
+
+ 25
+
+
+
+ system 8 gas grid tied pv
+
+ 25
+ 1
+
+
+
+ system 8 electricity
+
+ 26
+
+
+
+ system 8 electricity grid tied pv
+
+ 26
+ 1
+
+
diff --git a/hub/helpers/constants.py b/hub/helpers/constants.py
index 5827e6d7..80d40fae 100644
--- a/hub/helpers/constants.py
+++ b/hub/helpers/constants.py
@@ -303,6 +303,7 @@ GRID = 'Grid'
ONSITE_ELECTRICITY = 'Onsite Electricity'
PHOTOVOLTAIC = 'Photovoltaic'
BOILER = 'Boiler'
+FURNACE = 'Furnace'
HEAT_PUMP = 'Heat Pump'
BASEBOARD = 'Baseboard'
ELECTRICITY_GENERATOR = 'Electricity generator'
diff --git a/input_files/output_buildings_expanded.geojson b/input_files/output_buildings_expanded.geojson
deleted file mode 100644
index 43fd4d3f..00000000
--- a/input_files/output_buildings_expanded.geojson
+++ /dev/null
@@ -1,863 +0,0 @@
-{
- "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/tests/test_systems_catalog.py b/tests/test_systems_catalog.py
index 68401719..c9fef9a1 100644
--- a/tests/test_systems_catalog.py
+++ b/tests/test_systems_catalog.py
@@ -39,11 +39,11 @@ class TestSystemsCatalog(TestCase):
catalog_categories = catalog.names()
archetypes = catalog.names()
- self.assertEqual(13, len(archetypes['archetypes']))
+ self.assertEqual(34, len(archetypes['archetypes']))
systems = catalog.names('systems')
- self.assertEqual(17, len(systems['systems']))
+ self.assertEqual(39, len(systems['systems']))
generation_equipments = catalog.names('generation_equipments')
- self.assertEqual(27, len(generation_equipments['generation_equipments']))
+ self.assertEqual(49, len(generation_equipments['generation_equipments']))
with self.assertRaises(ValueError):
catalog.names('unknown')
@@ -54,4 +54,4 @@ class TestSystemsCatalog(TestCase):
with self.assertRaises(IndexError):
catalog.get_entry('unknown')
- print(catalog.entries())
+
diff --git a/tests/test_systems_factory.py b/tests/test_systems_factory.py
index 2e54171d..4a16d462 100644
--- a/tests/test_systems_factory.py
+++ b/tests/test_systems_factory.py
@@ -114,8 +114,8 @@ class TestSystemsFactory(TestCase):
ResultFactory('insel_monthly_energy_balance', self._city, self._output_path).enrich()
for building in self._city.buildings:
- building.energy_systems_archetype_name = ('Central 4 Pipes Air to Water Heat Pump and Gas Boiler with '
- 'Independent Water Heating and PV')
+ building.energy_systems_archetype_name = ('Central Hydronic Air and Gas Source Heating System with Unitary Split '
+ 'Cooling and Air Source HP DHW and Grid Tied PV')
EnergySystemsFactory('montreal_future', self._city).enrich()
# Need to assign energy systems to buildings:
for building in self._city.buildings:
@@ -123,13 +123,14 @@ class TestSystemsFactory(TestCase):
for energy_system in building.energy_systems:
if cte.HEATING in energy_system.demand_types:
_generation_system = cast(NonPvGenerationSystem, energy_system.generation_systems[0])
- _generation_system.heat_power = building.heating_peak_load[cte.YEAR][0]
+ _generation_system.nominal_heat_output = building.heating_peak_load[cte.YEAR][0]
if cte.COOLING in energy_system.demand_types:
_generation_system = cast(NonPvGenerationSystem, energy_system.generation_systems[0])
- _generation_system.cooling_power = building.cooling_peak_load[cte.YEAR][0]
+ _generation_system.nominal_cooling_output = building.cooling_peak_load[cte.YEAR][0]
for building in self._city.buildings:
self.assertLess(0, building.heating_consumption[cte.YEAR][0])
self.assertLess(0, building.cooling_consumption[cte.YEAR][0])
self.assertLess(0, building.domestic_hot_water_consumption[cte.YEAR][0])
- self.assertLess(0, building.onsite_electrical_production[cte.YEAR][0])
\ No newline at end of file
+ if 'PV' in building.energy_systems_archetype_name:
+ self.assertLess(0, building.onsite_electrical_production[cte.YEAR][0])
\ No newline at end of file