From 69a1af535bc4fd01fe70328c5337d3ea5b2392e0 Mon Sep 17 00:00:00 2001 From: s_ranjbar Date: Wed, 13 Nov 2024 12:11:11 +0100 Subject: [PATCH 1/3] feat: PV workflow updated and modified --- building_modelling/geojson_creator.py | 8 +- .../electricity_demand_calculator.py | 12 +- .../pv_assessment/pv_system_assessment.py | 235 +++++ .../pv_assessment/radiation_tilted.py | 142 +-- .../pv_assessment/solar_angles.py | 6 +- .../pv_assessment/solar_calculator.py | 221 +++++ .../random_assignation.py | 28 +- example_codes/pv_potential_assessment.py | 0 example_codes/pv_system_assessment.py | 86 ++ hub/city_model_structure/building.py | 92 +- .../montreal_future_systems.xml | 906 ++++++++++++++++-- input_files/output_buildings_expanded.geojson | 863 ----------------- tests/test_systems_catalog.py | 8 +- 13 files changed, 1508 insertions(+), 1099 deletions(-) create mode 100644 energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/pv_system_assessment.py create mode 100644 energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/solar_calculator.py create mode 100644 example_codes/pv_potential_assessment.py create mode 100644 example_codes/pv_system_assessment.py delete mode 100644 input_files/output_buildings_expanded.geojson 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/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_system_assessment.py b/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/pv_system_assessment.py new file mode 100644 index 00000000..963704b5 --- /dev/null +++ b/energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/pv_system_assessment.py @@ -0,0 +1,235 @@ +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, tilt_angle=None, solar_angles=None, + system_type=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: + :param system_catalogue_handler: + :param roof_percentage_coverage: + :param facade_coverage_percentage: + """ + self.building = building + 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 + 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(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): + 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) + 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): + building_hourly_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(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), + 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(8760): + transfer = total_hourly_pv_output[i] - building_hourly_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, + '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, + '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': + 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 index 31bd5636..8a5074bd 100644 --- 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 @@ -5,19 +5,18 @@ 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, + 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.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] + 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, 'ghi': self.ghi} + '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']) @@ -30,81 +29,84 @@ class RadiationTilted: self.i_oh = [] self.k_t = [] self.fraction_diffuse = [] - self.diffuse_horizontal = [] - self.beam_horizontal = [] + self.diffuse_hor = [] self.dni = [] - self.beam_tilted = [] - self.diffuse_tilted = [] - self.total_radiation_tilted = [] - self.calculate() + 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_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 - - 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 + 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 - self.df['diffuse fraction'] = self.fraction_diffuse + 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): - 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 + 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 - 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 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): - tilted_radiation = self.total_radiation_tilted - self.building.roofs[0].global_irradiance_tilted[cte.HOUR] = tilted_radiation + 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 index 560bd27c..c4c1a023 100644 --- 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 @@ -1,7 +1,6 @@ import math import pandas as pd from datetime import datetime -from pathlib import Path class CitySolarAngles: @@ -28,7 +27,7 @@ class CitySolarAngles: 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.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 @@ -44,8 +43,9 @@ class CitySolarAngles: 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_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 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..0ea04499 100644 --- a/energy_system_modelling_package/random_assignation.py +++ b/energy_system_modelling_package/random_assignation.py @@ -29,19 +29,21 @@ 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 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 } non_residential_systems_percentage = {'system 1 gas': 0, 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..0164d55a --- /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 + +# 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) +solar_angles = solar_parameters.solar_angles # Obtain solar angles for further analysis +solar_parameters.tilted_irradiance_calculator() +# # PV modelling building by building +for building in city.buildings: + PvSystemAssessment(building=building, + pv_system=None, + 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, + inverter_efficiency=0.95, + system_catalogue_handler='montreal_future', + roof_percentage_coverage=0.75, + facade_coverage_percentage=0, + csv_output=True, + output_path=pv_assessment_path).enrich() + +print('test') + + 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..e85ad3fb 100644 --- a/hub/data/energy_systems/montreal_future_systems.xml +++ b/hub/data/energy_systems/montreal_future_systems.xml @@ -684,14 +684,14 @@ 18 - template Air-to-Water heat pump with storage + template reversible 4-pipe air-to-water heat pump with storage heat pump - 2 + 2.5 True electricity Air @@ -737,7 +737,7 @@ 19 - template Groundwater-to-Water heat pump with storage + template reversible 4-pipe groundwater-to-water heat pump with storage heat pump @@ -778,7 +778,7 @@ 20 - template Water-to-Water heat pump with storage + template reversible 4-pipe water-to-water heat pump with storage heat pump @@ -897,7 +897,7 @@ 23 - template Air-to-Water heat pump + template reversible 4-pipe air-to-water heat pump heat pump @@ -912,7 +912,7 @@ - 4 + 4.5 @@ -948,7 +948,7 @@ 24 - template Groundwater-to-Water heat pump + template reversible 4-pipe groundwater-to-water heat pump heat pump @@ -987,7 +987,7 @@ 25 - template Water-to-Water heat pump + template reversible 4-pipe water-to-water heat pump heat pump @@ -1024,29 +1024,440 @@ True - + 26 - template Photovoltaic Module - photovoltaic + 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 + + + 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,216 @@ False + + 37 + template Photovoltaic Module + photovoltaic + + + + 0.2 + 20 + 45 + 800 + 25 + 1000 + 500 + 0.34 + 2.0 + 1.0 + + + False + + + 38 + Photovoltaic Module + photovoltaic + RE400CAA Pure 2 + REC + 305 + 0.206 + 20 + 44 + 800 + 25 + 1000 + 400 + 0.24 + 1.86 + 1.04 + + + False + + + 39 + Photovoltaic Module + photovoltaic + RE410CAA Pure 2 + REC + 312 + 0.211 + 20 + 44 + 800 + 25 + 1000 + 410 + 0.24 + 1.86 + 1.04 + + + False + + + 40 + Photovoltaic Module + photovoltaic + RE420CAA Pure 2 + REC + 320 + 0.217 + 20 + 44 + 800 + 25 + 1000 + 420 + 0.24 + 1.86 + 1.04 + + + False + + + 41 + Photovoltaic Module + photovoltaic + RE430CAA Pure 2 + REC + 327 + 0.222 + 20 + 44 + 800 + 25 + 1000 + 430 + 0.24 + 1.86 + 1.04 + + + False + + + 42 + Photovoltaic Module + photovoltaic + REC600AA Pro M + REC + 457 + 0.211 + 20 + 44 + 800 + 25 + 1000 + 600 + 0.24 + 2.17 + 1.3 + + + False + + + 43 + Photovoltaic Module + photovoltaic + REC610AA Pro M + REC + 464 + 0.215 + 20 + 44 + 800 + 25 + 1000 + 610 + 0.24 + 2.17 + 1.3 + + + False + + + 44 + Photovoltaic Module + photovoltaic + REC620AA Pro M + REC + 472 + 0.218 + 20 + 44 + 800 + 25 + 1000 + 620 + 0.24 + 2.17 + 1.3 + + + False + + + 45 + Photovoltaic Module + photovoltaic + REC630AA Pro M + REC + 480 + 0.222 + 20 + 44 + 800 + 25 + 1000 + 630 + 0.24 + 2.17 + 1.3 + + + False + + + 46 + 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 +1895,7 @@ sensible - 5000 + 0 @@ -1318,7 +1939,7 @@ electricity - 26 + 37 @@ -1401,6 +2022,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 +2142,7 @@ - 9 + 18 district heating network with air to water heat pump electrical boiler thermal storage tank schemas/ASHP+TES+GasBoiler.jpg @@ -1424,7 +2154,7 @@ - 10 + 19 district heating network with ground to water heat pump gas boiler thermal storage tank schemas/ASHP+TES+GasBoiler.jpg @@ -1436,7 +2166,7 @@ - 11 + 20 district heating network with ground to water heat pump electrical boiler thermal storage tank schemas/ASHP+TES+GasBoiler.jpg @@ -1448,7 +2178,7 @@ - 12 + 21 district heating network with water to water heat pump gas boiler thermal storage tank schemas/ASHP+TES+GasBoiler.jpg @@ -1460,7 +2190,7 @@ - 13 + 22 district heating network with water to water heat pump electrical boiler thermal storage tank schemas/ASHP+TES+GasBoiler.jpg @@ -1472,144 +2202,134 @@ - 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 - 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 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 PV 1 - 3 + 12 + 23 8 - 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 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 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 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 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 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..04307269 100644 --- a/tests/test_systems_catalog.py +++ b/tests/test_systems_catalog.py @@ -41,9 +41,9 @@ class TestSystemsCatalog(TestCase): archetypes = catalog.names() self.assertEqual(13, len(archetypes['archetypes'])) systems = catalog.names('systems') - self.assertEqual(17, len(systems['systems'])) + self.assertEqual(24, len(systems['systems'])) generation_equipments = catalog.names('generation_equipments') - self.assertEqual(27, len(generation_equipments['generation_equipments'])) + self.assertEqual(46, len(generation_equipments['generation_equipments'])) with self.assertRaises(ValueError): catalog.names('unknown') @@ -54,4 +54,6 @@ class TestSystemsCatalog(TestCase): with self.assertRaises(IndexError): catalog.get_entry('unknown') - print(catalog.entries()) + catalog_pv_generation_equipments = [component for component in + catalog.entries('generation_equipments') if + component.system_type == cte.PHOTOVOLTAIC] From b633dca635ceb464debb2ba98052a8c86913c0e7 Mon Sep 17 00:00:00 2001 From: s_ranjbar Date: Wed, 13 Nov 2024 14:30:08 +0100 Subject: [PATCH 2/3] fix: small changes before the meeting --- example_codes/pv_system_assessment.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/example_codes/pv_system_assessment.py b/example_codes/pv_system_assessment.py index 0164d55a..3053829b 100644 --- a/example_codes/pv_system_assessment.py +++ b/example_codes/pv_system_assessment.py @@ -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 @@ -64,6 +64,8 @@ solar_parameters = SolarCalculator(city=city, solar_angles = solar_parameters.solar_angles # Obtain solar angles for further analysis solar_parameters.tilted_irradiance_calculator() # # 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, @@ -78,7 +80,7 @@ for building in city.buildings: system_catalogue_handler='montreal_future', roof_percentage_coverage=0.75, facade_coverage_percentage=0, - csv_output=True, + csv_output=False, output_path=pv_assessment_path).enrich() print('test') From e4c761850b77d090e37b82171442e87f1cb937f4 Mon Sep 17 00:00:00 2001 From: s_ranjbar Date: Tue, 19 Nov 2024 13:25:54 +0100 Subject: [PATCH 3/3] fix: pv modelling workflow updated, montreal custom systems added to future catalogue --- .../montreal/archetype_cluster_1.py | 37 +- .../energy_system_sizing_factory.py | 28 - .../pv_assessment/pv_feasibility.py | 37 -- .../pv_assessment/pv_model.py | 42 -- .../pv_assessment/pv_sizing.py | 70 --- .../pv_assessment/pv_sizing_and_simulation.py | 59 -- .../pv_assessment/pv_system_assessment.py | 74 +-- .../pv_assessment/radiation_tilted.py | 112 ---- .../pv_assessment/solar_angles.py | 145 ----- .../random_assignation.py | 51 +- energy_system_retrofit.py | 37 +- example_codes/pv_system_assessment.py | 14 +- .../energy_systems/thermal_storage_system.py | 2 +- .../montreal_future_system_catalogue.py | 7 +- .../montreal_future_systems.xml | 574 ++++++++++++++++-- hub/helpers/constants.py | 1 + tests/test_systems_catalog.py | 10 +- tests/test_systems_factory.py | 11 +- 18 files changed, 634 insertions(+), 677 deletions(-) delete mode 100644 energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/pv_feasibility.py delete mode 100644 energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/pv_model.py delete mode 100644 energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/pv_sizing.py delete mode 100644 energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/pv_sizing_and_simulation.py delete mode 100644 energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/radiation_tilted.py delete mode 100644 energy_system_modelling_package/energy_system_modelling_factories/pv_assessment/solar_angles.py 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