comparison of vertical vs horizontal panels
This commit is contained in:
		
							parent
							
								
									ca4fcd8f49
								
							
						
					
					
						commit
						bba47e48b7
					
				| @ -2,7 +2,6 @@ import numpy as np | |||||||
| import pandas as pd | import pandas as pd | ||||||
| import logging | import logging | ||||||
| import math | import math | ||||||
| from tqdm import tqdm |  | ||||||
| 
 | 
 | ||||||
| import pvlib | import pvlib | ||||||
| 
 | 
 | ||||||
| @ -23,7 +22,7 @@ def get_location(c): | |||||||
|     return location |     return location | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def define_grid_layout(c): | def define_grid_layout(c, panel_tilt): | ||||||
|     # get number of panels required |     # get number of panels required | ||||||
|     no_of_panels = calculate_no_of_panels( |     no_of_panels = calculate_no_of_panels( | ||||||
|         c["array"]["system_size"], c["panel"]["peak_power"] |         c["array"]["system_size"], c["panel"]["peak_power"] | ||||||
| @ -33,7 +32,7 @@ def define_grid_layout(c): | |||||||
|     pitch = c["array"]["spacing"] + c["panel"]["dimensions"]["thickness"] |     pitch = c["array"]["spacing"] + c["panel"]["dimensions"]["thickness"] | ||||||
|     # calculate minimum pitch if we don't want panel overlap at all |     # calculate minimum pitch if we don't want panel overlap at all | ||||||
|     min_pitch = c["panel"]["dimensions"]["length"] * math.cos( |     min_pitch = c["panel"]["dimensions"]["length"] * math.cos( | ||||||
|         c["array"]["tilt"] / 180 * math.pi |         panel_tilt / 180 * math.pi | ||||||
|     ) |     ) | ||||||
|     if pitch < min_pitch: |     if pitch < min_pitch: | ||||||
|         logger.warning( |         logger.warning( | ||||||
| @ -129,8 +128,8 @@ def get_solar_data(c): | |||||||
|     return solar_positions, clearsky_data |     return solar_positions, clearsky_data | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def calculate_shading(c): | def calculate_energy_production_vertical(c): | ||||||
|     panel_coordinates, no_of_panels = define_grid_layout(c) |     panel_coordinates, no_of_panels = define_grid_layout(c, panel_tilt=90) | ||||||
|     solar_positions, clearsky_data = get_solar_data(c) |     solar_positions, clearsky_data = get_solar_data(c) | ||||||
| 
 | 
 | ||||||
|     # split the solar positions data into morning and afternoon, using solar azimuth of |     # split the solar positions data into morning and afternoon, using solar azimuth of | ||||||
| @ -146,10 +145,10 @@ def calculate_shading(c): | |||||||
|     # calculate delta between unique y coordinates of panels to get pitch |     # calculate delta between unique y coordinates of panels to get pitch | ||||||
|     pitch = np.unique(panel_coordinates["y"])[1] - np.unique(panel_coordinates["y"])[0] |     pitch = np.unique(panel_coordinates["y"])[1] - np.unique(panel_coordinates["y"])[0] | ||||||
|     surface_to_axis_offset = 0 |     surface_to_axis_offset = 0 | ||||||
|     shaded_row_rotation = c["array"]["tilt"] |     shaded_row_rotation = 90 | ||||||
|     shading_row_rotation = c["array"]["tilt"] |     shading_row_rotation = 90 | ||||||
|     axis_tilt = 0 |     axis_tilt = 0 | ||||||
|     axis_azimuth = c["array"]["front_face_azimuth"] |     axis_azimuth = 90 | ||||||
| 
 | 
 | ||||||
|     morning_shaded_fraction = pvlib.shading.shaded_fraction1d( |     morning_shaded_fraction = pvlib.shading.shaded_fraction1d( | ||||||
|         solar_zenith=morning_solar_positions["zenith"], |         solar_zenith=morning_solar_positions["zenith"], | ||||||
| @ -184,25 +183,27 @@ def calculate_shading(c): | |||||||
| 
 | 
 | ||||||
|     # calculate irradiance on plane of array |     # calculate irradiance on plane of array | ||||||
|     poa_front = pvlib.irradiance.get_total_irradiance( |     poa_front = pvlib.irradiance.get_total_irradiance( | ||||||
|         surface_tilt=c["array"]["tilt"], |         surface_tilt=90, | ||||||
|         surface_azimuth=c["array"]["front_face_azimuth"], |         surface_azimuth=axis_azimuth, | ||||||
|         solar_zenith=morning_solar_positions["zenith"], |         solar_zenith=morning_solar_positions["zenith"], | ||||||
|         solar_azimuth=morning_solar_positions["azimuth"], |         solar_azimuth=morning_solar_positions["azimuth"], | ||||||
|         dni=clearsky_data["dni"], |         dni=clearsky_data["dni"], | ||||||
|         ghi=clearsky_data["ghi"], |         ghi=clearsky_data["ghi"], | ||||||
|         dhi=clearsky_data["dhi"], |         dhi=clearsky_data["dhi"], | ||||||
|  |         albedo=0.5, | ||||||
|     ) |     ) | ||||||
|     # drop rows with poa_global NaN values |     # drop rows with poa_global NaN values | ||||||
|     poa_front = poa_front.dropna(subset=["poa_global"]) |     poa_front = poa_front.dropna(subset=["poa_global"]) | ||||||
| 
 | 
 | ||||||
|     poa_rear = pvlib.irradiance.get_total_irradiance( |     poa_rear = pvlib.irradiance.get_total_irradiance( | ||||||
|         surface_tilt=180 - c["array"]["tilt"], |         surface_tilt=180 - 90, | ||||||
|         surface_azimuth=c["array"]["front_face_azimuth"] + 180, |         surface_azimuth=axis_azimuth + 180, | ||||||
|         solar_zenith=afternoon_solar_positions["zenith"], |         solar_zenith=afternoon_solar_positions["zenith"], | ||||||
|         solar_azimuth=afternoon_solar_positions["azimuth"], |         solar_azimuth=afternoon_solar_positions["azimuth"], | ||||||
|         dni=clearsky_data["dni"], |         dni=clearsky_data["dni"], | ||||||
|         ghi=clearsky_data["ghi"], |         ghi=clearsky_data["ghi"], | ||||||
|         dhi=clearsky_data["dhi"], |         dhi=clearsky_data["dhi"], | ||||||
|  |         albedo=0.5, | ||||||
|     ) |     ) | ||||||
|     # drop rows with poa_global NaN values |     # drop rows with poa_global NaN values | ||||||
|     poa_rear = poa_rear.dropna(subset=["poa_global"]) |     poa_rear = poa_rear.dropna(subset=["poa_global"]) | ||||||
| @ -221,10 +222,74 @@ def calculate_shading(c): | |||||||
| 
 | 
 | ||||||
|     energy_front = effective_front * 15 / 60 / 1e3 |     energy_front = effective_front * 15 / 60 / 1e3 | ||||||
|     energy_rear = effective_rear * 15 / 60 / 1e3 |     energy_rear = effective_rear * 15 / 60 / 1e3 | ||||||
|     energy_total = energy_front.sum() + energy_rear.sum() |     total_hourly_energy_m2 = energy_front.add(energy_rear, fill_value=0) | ||||||
|  |     energy_total = total_hourly_energy_m2.sum() | ||||||
|     logger.info(f"Energy yield calculated: {energy_total} kWh/m2") |     logger.info(f"Energy yield calculated: {energy_total} kWh/m2") | ||||||
| 
 | 
 | ||||||
|     panel_area = c["panel"]["dimensions"]["length"] * c["panel"]["dimensions"]["width"] |     panel_area = c["panel"]["dimensions"]["length"] * c["panel"]["dimensions"]["width"] | ||||||
|     total_area = panel_area * no_of_panels |     total_area = panel_area * no_of_panels | ||||||
|     total_energy = energy_total * total_area |     total_hourly_energy = total_hourly_energy_m2 * total_area | ||||||
|  |     total_energy = total_hourly_energy.sum() | ||||||
|     logger.info(f"Total energy yield calculated: {total_energy} kWh") |     logger.info(f"Total energy yield calculated: {total_energy} kWh") | ||||||
|  | 
 | ||||||
|  |     return total_hourly_energy | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def calculate_energy_production_horizontal(c): | ||||||
|  |     panel_coordinates, no_of_panels = define_grid_layout(c, panel_tilt=0) | ||||||
|  |     solar_positions, clearsky_data = get_solar_data(c) | ||||||
|  | 
 | ||||||
|  |     # the first row is always not shaded so exclude | ||||||
|  |     no_of_rows = np.unique(panel_coordinates["y"]).shape[0] | ||||||
|  |     no_of_shaded_rows = no_of_rows - 1 | ||||||
|  | 
 | ||||||
|  |     collector_width = c["panel"]["dimensions"]["length"] | ||||||
|  |     # calculate delta between unique y coordinates of panels to get pitch | ||||||
|  |     pitch = np.unique(panel_coordinates["y"])[1] - np.unique(panel_coordinates["y"])[0] | ||||||
|  |     surface_to_axis_offset = 0 | ||||||
|  |     shaded_row_rotation = 0 | ||||||
|  |     shading_row_rotation = 0 | ||||||
|  |     axis_tilt = 0 | ||||||
|  |     axis_azimuth = 180  # south facing | ||||||
|  | 
 | ||||||
|  |     shaded_fraction = pvlib.shading.shaded_fraction1d( | ||||||
|  |         solar_zenith=solar_positions["zenith"], | ||||||
|  |         solar_azimuth=solar_positions["azimuth"], | ||||||
|  |         axis_azimuth=axis_azimuth, | ||||||
|  |         shaded_row_rotation=shaded_row_rotation, | ||||||
|  |         shading_row_rotation=shading_row_rotation, | ||||||
|  |         collector_width=collector_width, | ||||||
|  |         pitch=pitch, | ||||||
|  |         surface_to_axis_offset=surface_to_axis_offset, | ||||||
|  |         axis_tilt=axis_tilt, | ||||||
|  |     ) | ||||||
|  |     shaded_fraction = shaded_fraction * no_of_shaded_rows / no_of_rows | ||||||
|  |     logger.info(f"Shaded fraction calculated for solar positions.") | ||||||
|  | 
 | ||||||
|  |     poa = pvlib.irradiance.get_total_irradiance( | ||||||
|  |         surface_tilt=0, | ||||||
|  |         surface_azimuth=axis_azimuth, | ||||||
|  |         solar_zenith=solar_positions["zenith"], | ||||||
|  |         solar_azimuth=solar_positions["azimuth"], | ||||||
|  |         dni=clearsky_data["dni"], | ||||||
|  |         ghi=clearsky_data["ghi"], | ||||||
|  |         dhi=clearsky_data["dhi"], | ||||||
|  |         surface_type="urban", | ||||||
|  |     ) | ||||||
|  |     poa = poa.dropna(subset=["poa_global"]) | ||||||
|  | 
 | ||||||
|  |     effective_front = ( | ||||||
|  |         poa["poa_global"] * (1 - shaded_fraction) * c["panel"]["efficiency"] | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     total_hourly_energy_m2 = effective_front * 15 / 60 / 1e3 | ||||||
|  |     energy_total = total_hourly_energy_m2.sum() | ||||||
|  |     logger.info(f"Energy yield calculated: {energy_total} kWh/m2") | ||||||
|  | 
 | ||||||
|  |     panel_area = c["panel"]["dimensions"]["length"] * c["panel"]["dimensions"]["width"] | ||||||
|  |     total_area = panel_area * no_of_panels | ||||||
|  |     total_hourly_energy = total_hourly_energy_m2 * total_area | ||||||
|  |     total_energy = total_hourly_energy.sum() | ||||||
|  |     logger.info(f"Total energy yield calculated: {total_energy} kWh") | ||||||
|  | 
 | ||||||
|  |     return total_hourly_energy | ||||||
|  | |||||||
| @ -1,9 +1,8 @@ | |||||||
| array: | array: | ||||||
|   system_size: 400 # in kWp |   system_size: 400 # in kWp | ||||||
|   spacing: 0.5 # spacing between adjacent panel rows in m |   spacing: 1 # spacing between adjacent panel rows in m | ||||||
|   edge_setback: 1.8 # distance from the edge of the roof to the array |   edge_setback: 1.8 # distance from the edge of the roof to the array | ||||||
|   front_face_azimuth: 90 # 90=east, 180=south, 270=west |   roof_slope: 0 | ||||||
|   tilt: 90 # just 0 and 90 are supported for now |  | ||||||
|   slope: 0 # degrees from horizontal (+ve means shaded row is higher than the row in front) |   slope: 0 # degrees from horizontal (+ve means shaded row is higher than the row in front) | ||||||
| 
 | 
 | ||||||
| simulation_date_time: | simulation_date_time: | ||||||
|  | |||||||
							
								
								
									
										43
									
								
								main.py
									
									
									
									
									
								
							
							
						
						
									
										43
									
								
								main.py
									
									
									
									
									
								
							| @ -1,7 +1,12 @@ | |||||||
| # %% | # %% | ||||||
| import yaml | import yaml | ||||||
| import logging | import logging | ||||||
| from Utilities.Shading import calculate_shading | import numpy as np | ||||||
|  | import matplotlib.pyplot as pl | ||||||
|  | from Utilities.Shading import ( | ||||||
|  |     calculate_energy_production_horizontal, | ||||||
|  |     calculate_energy_production_vertical, | ||||||
|  | ) | ||||||
| 
 | 
 | ||||||
| logging.basicConfig( | logging.basicConfig( | ||||||
|     level=logging.INFO, |     level=logging.INFO, | ||||||
| @ -24,7 +29,37 @@ with open(config_path, "r") as file: | |||||||
| logger.info("Configuration loaded successfully.") | logger.info("Configuration loaded successfully.") | ||||||
| logger.debug(f"Configuration: {c}") | logger.debug(f"Configuration: {c}") | ||||||
| 
 | 
 | ||||||
| shading = calculate_shading(c) | # calculate energy production for horizontal and vertical panels | ||||||
| logger.info("Shading calculation completed successfully.") | horizontal_energy = calculate_energy_production_horizontal(c) | ||||||
|  | logger.info("Energy production for horizontal panels calculated successfully.") | ||||||
|  | logger.debug(f"Horizontal Energy Production: {horizontal_energy.sum()}") | ||||||
| 
 | 
 | ||||||
| # %% | vertical_energy = calculate_energy_production_vertical(c) | ||||||
|  | logger.info("Energy production for vertical panels calculated successfully.") | ||||||
|  | logger.debug(f"Vertical Energy Production: {vertical_energy.sum()}") | ||||||
|  | 
 | ||||||
|  | NOVA_scaledown = 0.75 | ||||||
|  | 
 | ||||||
|  | horizontal_energy_scaled = horizontal_energy * NOVA_scaledown | ||||||
|  | logger.info("Energy production for horizontal panels scaled down to NOVA requirement.") | ||||||
|  | 
 | ||||||
|  | logger.info( | ||||||
|  |     f"Energy production for horizontal panels: {np.round(horizontal_energy_scaled.sum(),0)} kWh" | ||||||
|  | ) | ||||||
|  | logger.info( | ||||||
|  |     f"Energy production for vertical panels: {np.round(vertical_energy.sum(),0)} kWh" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | # overlay horizontal and vertical energy production | ||||||
|  | pl.figure(figsize=(10, 6)) | ||||||
|  | pl.plot( | ||||||
|  |     horizontal_energy_scaled.index, | ||||||
|  |     horizontal_energy_scaled.values, | ||||||
|  |     label="Horizontal Panels", | ||||||
|  | ) | ||||||
|  | pl.plot(vertical_energy.index, vertical_energy.values, label="Vertical Panels") | ||||||
|  | pl.title("Energy Production Comparison") | ||||||
|  | pl.xlabel("Time") | ||||||
|  | pl.ylabel("Energy Production (kWh)") | ||||||
|  | pl.legend() | ||||||
|  | pl.show() | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user