From 69a1af535bc4fd01fe70328c5337d3ea5b2392e0 Mon Sep 17 00:00:00 2001 From: s_ranjbar Date: Wed, 13 Nov 2024 12:11:11 +0100 Subject: [PATCH] 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]