use PVWatts to simulate panel output
This commit is contained in:
		
							parent
							
								
									ebae0aecfe
								
							
						
					
					
						commit
						05186bd77f
					
				| @ -18,16 +18,18 @@ def optimise_vertical_panel_pitch(c): | |||||||
|         """ |         """ | ||||||
|         c["array"]["spacing"] = pitch |         c["array"]["spacing"] = pitch | ||||||
|         logging.info(f"Optimizing with pitch: {pitch}m") |         logging.info(f"Optimizing with pitch: {pitch}m") | ||||||
|         vertical_energy = calculate_energy_production_vertical(c) |         vertical_energy, _ = calculate_energy_production_vertical(c) | ||||||
|         total_energy_yield = vertical_energy.sum() |         total_energy_yield = vertical_energy.sum() | ||||||
|         logger.info(f"Total energy yield for pitch {pitch}m: {total_energy_yield}kWh") |         logger.info(f"Total energy yield for pitch {pitch}m: {total_energy_yield}kWh") | ||||||
|         return -total_energy_yield |         return -total_energy_yield | ||||||
| 
 | 
 | ||||||
|     # perform minimization |     # perform minimization | ||||||
|     initial_pitch = c["array"]["spacing"] |     initial_pitch = c["array"]["spacing"] | ||||||
|     result = minimize(objective, initial_pitch, bounds=[(0, 90)]) |     result = minimize( | ||||||
|  |         objective, initial_pitch, bounds=[(0, 5)], tol=1e-8, options={"eps": 0.5} | ||||||
|  |     ) | ||||||
|     optimal_pitch = result.x[0] |     optimal_pitch = result.x[0] | ||||||
|     c["array"]["spacing"] = optimal_pitch |     c["array"]["spacing"] = optimal_pitch | ||||||
|     logger.info(f"Optimal pitch found: {optimal_pitch}m") |     logger.info(f"Optimal pitch found: {optimal_pitch}m") | ||||||
|     vetical_energy = calculate_energy_production_vertical(c) |     vetical_energy, no_of_panels = calculate_energy_production_vertical(c) | ||||||
|     return (optimal_pitch, vetical_energy) |     return (optimal_pitch, vetical_energy, no_of_panels) | ||||||
|  | |||||||
| @ -132,11 +132,6 @@ def calculate_energy_production_vertical(c): | |||||||
|     panel_coordinates, no_of_panels = define_grid_layout(c, panel_tilt=90) |     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 |  | ||||||
|     # 180 degrees as the threshold |  | ||||||
|     morning_solar_positions = solar_positions[solar_positions["azimuth"] <= 180] |  | ||||||
|     afternoon_solar_positions = solar_positions[solar_positions["azimuth"] > 180] |  | ||||||
| 
 |  | ||||||
|     # the first row is always not shaded so exclude |     # the first row is always not shaded so exclude | ||||||
|     no_of_rows = np.unique(panel_coordinates["y"]).shape[0] |     no_of_rows = np.unique(panel_coordinates["y"]).shape[0] | ||||||
|     no_of_shaded_rows = no_of_rows - 1 |     no_of_shaded_rows = no_of_rows - 1 | ||||||
| @ -207,31 +202,31 @@ def calculate_energy_production_vertical(c): | |||||||
|     # 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"]) | ||||||
| 
 | 
 | ||||||
|     effective_front = ( |     effective_front = poa_front["poa_global"] * (1 - morning_shaded_fraction) | ||||||
|         poa_front["poa_global"] |  | ||||||
|         * (1 - morning_shaded_fraction) |  | ||||||
|         * c["panel"]["efficiency"] |  | ||||||
|     ) |  | ||||||
|     effective_rear = ( |     effective_rear = ( | ||||||
|         poa_rear["poa_global"] |         poa_rear["poa_global"] | ||||||
|         * (1 - afternoon_shaded_fraction) |         * (1 - afternoon_shaded_fraction) | ||||||
|         * c["panel"]["bifaciality"] |         * c["panel"]["bifaciality"] | ||||||
|         * c["panel"]["efficiency"] |  | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     energy_front = effective_front * 15 / 60 / 1e3 |     total_hourly_irradiance = effective_front + effective_rear | ||||||
|     energy_rear = effective_rear * 15 / 60 / 1e3 |     system_size = c["panel"]["peak_power"] * no_of_panels | ||||||
|     total_hourly_energy_m2 = energy_front + energy_rear |     pdc0 = system_size | ||||||
|     energy_total = total_hourly_energy_m2.sum() |     gamma_pdc = c["panel"]["temperature_coefficient"] | ||||||
|     logger.info(f"Energy yield calculated: {energy_total} kWh/m2") |     temp_cell = c["panel"]["nominal_operating_cell_temperature"] | ||||||
|  |     pdc = pvlib.pvsystem.pvwatts_dc( | ||||||
|  |         pdc0=pdc0, | ||||||
|  |         gamma_pdc=gamma_pdc, | ||||||
|  |         temp_cell=temp_cell, | ||||||
|  |         g_poa_effective=total_hourly_irradiance, | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     total_hourly_energy = pdc * 15 / 60 / 1e3  # convert to kWh | ||||||
| 
 | 
 | ||||||
|     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() |     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 |     return total_hourly_energy, no_of_panels | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def calculate_energy_production_horizontal(c): | def calculate_energy_production_horizontal(c): | ||||||
| @ -263,7 +258,6 @@ def calculate_energy_production_horizontal(c): | |||||||
|         axis_tilt=axis_tilt, |         axis_tilt=axis_tilt, | ||||||
|     ) |     ) | ||||||
|     shaded_fraction = shaded_fraction * no_of_shaded_rows / no_of_rows |     shaded_fraction = shaded_fraction * no_of_shaded_rows / no_of_rows | ||||||
|     logger.info(f"Shaded fraction calculated for solar positions: {shaded_fraction}") |  | ||||||
| 
 | 
 | ||||||
|     poa = pvlib.irradiance.get_total_irradiance( |     poa = pvlib.irradiance.get_total_irradiance( | ||||||
|         surface_tilt=0, |         surface_tilt=0, | ||||||
| @ -277,18 +271,22 @@ def calculate_energy_production_horizontal(c): | |||||||
|     ) |     ) | ||||||
|     poa = poa.dropna(subset=["poa_global"]) |     poa = poa.dropna(subset=["poa_global"]) | ||||||
| 
 | 
 | ||||||
|     effective_front = ( |     effective_front = poa["poa_global"] * (1 - shaded_fraction) | ||||||
|         poa["poa_global"] * (1 - shaded_fraction) * c["panel"]["efficiency"] | 
 | ||||||
|  |     system_size = c["panel"]["peak_power"] * no_of_panels | ||||||
|  | 
 | ||||||
|  |     pdc0 = system_size | ||||||
|  |     gamma_pdc = c["panel"]["temperature_coefficient"] | ||||||
|  |     temp_cell = c["panel"]["nominal_operating_cell_temperature"] | ||||||
|  |     pdc = pvlib.pvsystem.pvwatts_dc( | ||||||
|  |         pdc0=pdc0, | ||||||
|  |         gamma_pdc=gamma_pdc, | ||||||
|  |         temp_cell=temp_cell, | ||||||
|  |         g_poa_effective=effective_front, | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     total_hourly_energy_m2 = effective_front * 15 / 60 / 1e3 |     total_hourly_energy = pdc * 15 / 60 / 1e3  # convert to kWh | ||||||
|     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() |     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 |     return total_hourly_energy, no_of_panels | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| array: | array: | ||||||
|   system_size: 900 # in kWp |   system_size: 900 # in kWp | ||||||
|   spacing: 1 # spacing between adjacent panel rows in m |   spacing: 21.8 # 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 | ||||||
|   roof_slope: 0 |   roof_slope: 0 | ||||||
|   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) | ||||||
| @ -30,3 +30,6 @@ panel: | |||||||
|     length: 2.384 |     length: 2.384 | ||||||
|     width: 1.303 |     width: 1.303 | ||||||
|     thickness: 0.033 |     thickness: 0.033 | ||||||
|  |   temperature_coefficient: -0.0029 # /°C | ||||||
|  |   nominal_operating_cell_temperature: 43 # °C | ||||||
|  | 
 | ||||||
|  | |||||||
							
								
								
									
										13
									
								
								main.py
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								main.py
									
									
									
									
									
								
							| @ -3,7 +3,10 @@ import yaml | |||||||
| import logging | import logging | ||||||
| import numpy as np | import numpy as np | ||||||
| import matplotlib.pyplot as pl | import matplotlib.pyplot as pl | ||||||
| from Utilities.Shading import calculate_energy_production_horizontal | from Utilities.Shading import ( | ||||||
|  |     calculate_energy_production_horizontal, | ||||||
|  |     calculate_energy_production_vertical, | ||||||
|  | ) | ||||||
| from Utilities.Optimisation import optimise_vertical_panel_pitch | from Utilities.Optimisation import optimise_vertical_panel_pitch | ||||||
| 
 | 
 | ||||||
| logging.basicConfig( | logging.basicConfig( | ||||||
| @ -27,15 +30,17 @@ 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}") | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| # calculate energy production for horizontal and vertical panels | # calculate energy production for horizontal and vertical panels | ||||||
| optimal_pitch, vertical_energy = optimise_vertical_panel_pitch(c) | optimal_pitch, vertical_energy, no_of_panels_vertical = optimise_vertical_panel_pitch(c) | ||||||
| logger.info("Energy production for vertical panels calculated successfully.") | logger.info("Energy production for vertical panels calculated successfully.") | ||||||
| logger.debug(f"Vertical Energy Production: {vertical_energy.sum()}") | logger.debug(f"Vertical Energy Production: {vertical_energy.sum()}") | ||||||
|  | logger.debug("Number of panels: %d", no_of_panels_vertical) | ||||||
| 
 | 
 | ||||||
| horizontal_energy = calculate_energy_production_horizontal(c) | horizontal_energy, no_of_panels_horizontal = calculate_energy_production_horizontal(c) | ||||||
| logger.info("Energy production for horizontal panels calculated successfully.") | logger.info("Energy production for horizontal panels calculated successfully.") | ||||||
| logger.debug(f"Horizontal Energy Production: {horizontal_energy.sum()}") | logger.debug(f"Horizontal Energy Production: {horizontal_energy.sum()}") | ||||||
| 
 | logger.debug("Number of panels: %d", no_of_panels_horizontal) | ||||||
| 
 | 
 | ||||||
| NOVA_scaledown = 0.75 | NOVA_scaledown = 0.75 | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user