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/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 index 963704b5..ce209581 100644 --- 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 @@ -8,15 +8,14 @@ from hub.helpers.monthly_values import MonthlyValues class PvSystemAssessment: - def __init__(self, building=None, pv_system=None, battery=None, tilt_angle=None, solar_angles=None, - system_type=None, pv_installation_type=None, simulation_model_type=None, module_model_name=None, + 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 system_type: :param simulation_model_type: :param module_model_name: :param inverter_efficiency: @@ -25,9 +24,9 @@ class PvSystemAssessment: :param facade_coverage_percentage: """ self.building = building + self.electricity_demand = electricity_demand self.tilt_angle = tilt_angle self.solar_angles = solar_angles - self.system_type = system_type self.pv_installation_type = pv_installation_type self.simulation_model_type = simulation_model_type self.module_model_name = module_model_name @@ -58,19 +57,16 @@ class PvSystemAssessment: self.battery = storage_system @staticmethod - def explicit_model(standard_test_condition_maximum_power, standard_test_condition_radiation, - cell_temperature_coefficient, standard_test_condition_cell_temperature, nominal_radiation, - nominal_cell_temperature, nominal_ambient_temperature, inverter_efficiency, number_of_panels, - irradiance, outdoor_temperature): + def explicit_model(pv_system, inverter_efficiency, number_of_panels, irradiance, outdoor_temperature): inverter_efficiency = inverter_efficiency - stc_power = float(standard_test_condition_maximum_power) - stc_irradiance = float(standard_test_condition_radiation) - cell_temperature_coefficient = float(cell_temperature_coefficient) / 100 if ( - cell_temperature_coefficient is not None) else None - stc_t_cell = float(standard_test_condition_cell_temperature) - nominal_condition_irradiance = float(nominal_radiation) - nominal_condition_cell_temperature = float(nominal_cell_temperature) - nominal_t_out = float(nominal_ambient_temperature) + 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 = [] @@ -101,7 +97,8 @@ class PvSystemAssessment: (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 = ((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) @@ -128,17 +125,20 @@ class PvSystemAssessment: 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 + # 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 + # Replace the old generation system with the new one energy_system.generation_systems[idx] = new_system def grid_tied_system(self): - building_hourly_electricity_demand = [demand / cte.WATTS_HOUR_TO_JULES for demand in + 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 @@ -147,19 +147,7 @@ class PvSystemAssessment: np, ns = self.rooftop_sizing() if self.simulation_model_type == 'explicit': rooftop_number_of_panels = np * ns - rooftop_pv_output = self.explicit_model(standard_test_condition_maximum_power= - float(self.pv_system.standard_test_condition_maximum_power), - standard_test_condition_radiation= - float(self.pv_system.standard_test_condition_radiation), - cell_temperature_coefficient= - float(self.pv_system.cell_temperature_coefficient) / 100, - standard_test_condition_cell_temperature= - float(self.pv_system.standard_test_condition_cell_temperature), - nominal_radiation=float(self.pv_system.nominal_radiation), - nominal_cell_temperature=float( - self.pv_system.nominal_cell_temperature), - nominal_ambient_temperature= - float(self.pv_system.nominal_ambient_temperature), + 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[ @@ -170,8 +158,8 @@ class PvSystemAssessment: 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(8760): - transfer = total_hourly_pv_output[i] - building_hourly_electricity_demand[i] + for i in range(len(electricity_demand)): + transfer = total_hourly_pv_output[i] - electricity_demand[i] if transfer > 0: exported_electricity[i] = transfer else: @@ -185,17 +173,20 @@ class PvSystemAssessment: 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': building_hourly_electricity_demand, + '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): - if self.system_type.lower() == 'grid_tied': + 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 @@ -221,15 +212,14 @@ class PvSystemAssessment: # 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 + # 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 + # Write an empty row for separation writer.writerow([]) - # Write the header for the list values + # Write the header for the list values writer.writerow(list_value_keys) - # Write each row for the lists + # 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 8a5074bd..00000000 --- a/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/radiation_tilted.py +++ /dev/null @@ -1,112 +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, solar_constant=1366.1, maximum_clearness_index=1, - min_cos_zenith=0.065, maximum_zenith_angle=87): - self.building = building - self.tilt_angle = tilt_angle - self.zeniths = solar_angles['zenith'].tolist() - self.incidents = solar_angles['incident angle'].tolist() - self.date_time = solar_angles['DateTime'].tolist() - self.ast = solar_angles['AST'].tolist() - self.solar_azimuth = solar_angles['solar azimuth'].tolist() - self.solar_altitude = solar_angles['solar altitude'].tolist() - 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} - 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_hor = [] - self.dni = [] - self.tilted_diffuse = [] - self.tilted_beam = [] - self.total_tilted = [] - - 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.i_oh.append(self.i_on[i] * max(math.cos(math.radians(self.zeniths[i])), self.min_cos_zenith)) - - self.df['extraterrestrial normal radiation (Wh/m2)'] = self.i_on - self.df['extraterrestrial radiation on horizontal (Wh/m2)'] = self.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) - self.tilted_beam.append(beam_tilted) - diffuse_tilted = diffuse_horizontal * ((1 + math.cos(math.radians(self.tilt_angle))) / 2) - self.tilted_diffuse.append(diffuse_tilted) - total_radiation_tilted = beam_tilted + diffuse_tilted - return total_radiation_tilted - - def enrich(self): - self.dni_extra() - ghi = self.building.roofs[0].global_irradiance[cte.HOUR] - hourly_tilted_radiation = [] - for i in range(len(ghi)): - k_t = self.clearness_index(ghi=ghi[i], i_oh=self.i_oh[i]) - self.k_t.append(k_t) - fraction_diffuse = self.diffuse_fraction(k_t, self.zeniths[i]) - self.fraction_diffuse.append(fraction_diffuse) - diffuse_horizontal, dni = self.radiation_components_horizontal(ghi=ghi[i], - fraction_diffuse=fraction_diffuse, - zenith=self.zeniths[i]) - self.diffuse_hor.append(diffuse_horizontal) - self.dni.append(dni) - - hourly_tilted_radiation.append(int(self.radiation_components_tilted(diffuse_horizontal=diffuse_horizontal, - dni=dni, - incident_angle=self.incidents[i]))) - self.total_tilted.append(hourly_tilted_radiation[i]) - self.building.roofs[0].global_irradiance_tilted[cte.HOUR] = hourly_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])] - self.df['k_t'] = self.k_t - self.df['diffuse_frac'] = self.fraction_diffuse - self.df['diff_hor'] = self.diffuse_hor - self.df['dni'] = self.dni - self.df['diff_tilted'] = self.tilted_diffuse - self.df['diff_beam'] = self.tilted_beam - self.df['total_tilted'] = self.total_tilted - self.df['ghi'] = ghi - self.df.to_csv(f'{self.building.name}_old_radiation.csv') 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 c4c1a023..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 - - -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='2023-12-31 23:00', 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) % 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 - - @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/random_assignation.py b/energy_system_modelling_package/random_assignation.py index 0ea04499..16cb7745 100644 --- a/energy_system_modelling_package/random_assignation.py +++ b/energy_system_modelling_package/random_assignation.py @@ -29,21 +29,41 @@ residential_systems_percentage = {'system 1 gas': 15, 'system 8 electricity': 35} residential_new_systems_percentage = { - 'Central Hydronic Air and Gas Source Heating System with Unitary Split Cooling and Air Source HP DHW and PV': 50, - 'Central Hydronic Air and Electricity Source Heating System with Unitary Split Cooling and Air Source HP DHW and PV': 0, - 'Central Hydronic Ground and Gas Source Heating System with Unitary Split Cooling and Air Source HP DHW and PV': 0, - 'Central Hydronic Ground and Electricity Source Heating System with Unitary Split Cooling and Air Source HP DHW ' - 'and PV': 0, - 'Central Hydronic Water and Gas Source Heating System with Unitary Split Cooling and Air Source HP DHW and PV': 0, - 'Central Hydronic Water and Electricity Source Heating System with Unitary Split Cooling and Air Source HP DHW ' - 'and 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': 50 + '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, @@ -120,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_system_assessment.py b/example_codes/pv_system_assessment.py index 3053829b..8c10642e 100644 --- a/example_codes/pv_system_assessment.py +++ b/example_codes/pv_system_assessment.py @@ -15,7 +15,7 @@ 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') @@ -44,7 +44,7 @@ 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) +# 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 @@ -60,9 +60,10 @@ EnergySystemsFactory('montreal_future', city).enrich() tilt_angle = 37 solar_parameters = SolarCalculator(city=city, surface_azimuth_angle=180, - tilt_angle=tilt_angle) + tilt_angle=tilt_angle, + standard_meridian=-75) solar_angles = solar_parameters.solar_angles # Obtain solar angles for further analysis -solar_parameters.tilted_irradiance_calculator() +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'] @@ -72,10 +73,9 @@ for building in city.buildings: battery=None, tilt_angle=tilt_angle, solar_angles=solar_angles, - system_type='grid_tied', pv_installation_type='rooftop', simulation_model_type='explicit', - module_model_name=None, + module_model_name='REC640AA Pro M', inverter_efficiency=0.95, system_catalogue_handler='montreal_future', roof_percentage_coverage=0.75, @@ -83,6 +83,4 @@ for building in city.buildings: csv_output=False, output_path=pv_assessment_path).enrich() -print('test') - 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/data/energy_systems/montreal_future_systems.xml b/hub/data/energy_systems/montreal_future_systems.xml index e85ad3fb..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,7 +683,7 @@ False - 18 + 18 template reversible 4-pipe air-to-water heat pump with storage heat pump @@ -736,7 +736,7 @@ True - 19 + 19 template reversible 4-pipe groundwater-to-water heat pump with storage heat pump @@ -777,7 +777,7 @@ True - 20 + 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,7 +896,7 @@ False - 23 + 23 template reversible 4-pipe air-to-water heat pump heat pump @@ -947,7 +947,7 @@ True - 24 + 24 template reversible 4-pipe groundwater-to-water heat pump heat pump @@ -986,7 +986,7 @@ True - 25 + 25 template reversible 4-pipe water-to-water heat pump heat pump @@ -1025,7 +1025,7 @@ True - 26 + 26 template reversible 2-pipe air-to-water heat pump with storage heat pump @@ -1078,7 +1078,7 @@ False - 27 + 27 template reversible 2-pipe groundwater-to-water heat pump with storage heat pump @@ -1119,7 +1119,7 @@ False - 28 + 28 template reversible 2-pipe water-to-water heat pump with storage heat pump @@ -1160,7 +1160,7 @@ False - 29 + 29 template reversible 2-pipe air-to-water heat pump heat pump @@ -1211,7 +1211,7 @@ False - 30 + 30 template reversible 2-pipe groundwater-to-water heat pump heat pump @@ -1250,7 +1250,7 @@ False - 31 + 31 template reversible 2-pipe water-to-water heat pump heat pump @@ -1289,7 +1289,7 @@ False - 32 + 32 template air-to-water heating heat pump heat pump @@ -1334,7 +1334,7 @@ False - 33 + 33 template groundwater-to-water heating heat pump heat pump @@ -1373,7 +1373,7 @@ False - 34 + 34 template water-to-water heating heat pump heat pump @@ -1412,7 +1412,7 @@ False - 35 + 35 template unitary split system heat pump @@ -1457,7 +1457,7 @@ False - 36 + 36 template domestic hot water heat pump heat pump @@ -1503,8 +1503,125 @@ 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 + - 37 + 40 template Photovoltaic Module photovoltaic @@ -1525,7 +1642,7 @@ False - 38 + 41 Photovoltaic Module photovoltaic RE400CAA Pure 2 @@ -1546,7 +1663,7 @@ False - 39 + 42 Photovoltaic Module photovoltaic RE410CAA Pure 2 @@ -1567,7 +1684,7 @@ False - 40 + 43 Photovoltaic Module photovoltaic RE420CAA Pure 2 @@ -1588,7 +1705,7 @@ False - 41 + 44 Photovoltaic Module photovoltaic RE430CAA Pure 2 @@ -1609,7 +1726,7 @@ False - 42 + 45 Photovoltaic Module photovoltaic REC600AA Pro M @@ -1630,7 +1747,7 @@ False - 43 + 46 Photovoltaic Module photovoltaic REC610AA Pro M @@ -1651,7 +1768,7 @@ False - 44 + 47 Photovoltaic Module photovoltaic REC620AA Pro M @@ -1672,7 +1789,7 @@ False - 45 + 48 Photovoltaic Module photovoltaic REC630AA Pro M @@ -1693,7 +1810,7 @@ False - 46 + 49 Photovoltaic Module photovoltaic REC640AA Pro M @@ -1939,7 +2056,7 @@ electricity - 37 + 40 @@ -2223,11 +2340,186 @@ 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 Hydronic Air and Gas Source Heating System with Unitary Split Cooling and Air Source HP DHW and PV + Central Hydronic Air and Gas Source Heating System with Unitary Split Cooling and Air Source HP DHW and Grid Tied PV 1 11 @@ -2236,16 +2528,16 @@ - Central Hydronic Air and Electricity Source Heating System with Unitary Split Cooling and Air Source HP DHW and PV + Central Hydronic Air and Electricity Source Heating System with Unitary Split Cooling and Air Source HP DHW and Grid Tied PV 1 12 23 - 8 + 24 - Central Hydronic Ground and Gas Source Heating System with Unitary Split Cooling and Air Source HP DHW and PV + Central Hydronic Ground and Gas Source Heating System with Unitary Split Cooling and Air Source HP DHW and Grid Tied PV 1 13 @@ -2254,7 +2546,7 @@ - Central Hydronic Ground and Electricity Source Heating System with Unitary Split Cooling and Air Source HP DHW and PV + Central Hydronic Ground and Electricity Source Heating System with Unitary Split Cooling and Air Source HP DHW and Grid Tied PV 1 14 @@ -2263,7 +2555,7 @@ - Central Hydronic Water and Gas Source Heating System with Unitary Split Cooling and Air Source HP DHW and PV + Central Hydronic Water and Gas Source Heating System with Unitary Split Cooling and Air Source HP DHW and Grid Tied PV 1 15 @@ -2272,7 +2564,7 @@ - Central Hydronic Water and Electricity Source Heating System with Unitary Split Cooling and Air Source HP DHW and PV + Central Hydronic Water and Electricity Source Heating System with Unitary Split Cooling and Air Source HP DHW and Grid Tied PV 1 16 @@ -2329,11 +2621,165 @@ - Grid-Tied 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/tests/test_systems_catalog.py b/tests/test_systems_catalog.py index 04307269..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(24, len(systems['systems'])) + self.assertEqual(39, len(systems['systems'])) generation_equipments = catalog.names('generation_equipments') - self.assertEqual(46, len(generation_equipments['generation_equipments'])) + self.assertEqual(49, len(generation_equipments['generation_equipments'])) with self.assertRaises(ValueError): catalog.names('unknown') @@ -54,6 +54,4 @@ class TestSystemsCatalog(TestCase): with self.assertRaises(IndexError): catalog.get_entry('unknown') - catalog_pv_generation_equipments = [component for component in - catalog.entries('generation_equipments') if - component.system_type == cte.PHOTOVOLTAIC] + 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