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