Skip to content

Add agrivPV gallery example #2459

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
May 27, 2025
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 156 additions & 0 deletions docs/examples/agrivoltaics/plot_agrivoltaics_ground_irradiance.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
"""
AgriPV with infinite sheds
==========================

Irradiance at crop level between rows
"""

# %%
# This example demonstrates how to calculate irradiance at crop level
# for an agriPV system using pvlib's infinite sheds model.
# For an overview of agrivPV concepts and performance, the reader
# is referred to :doi:`10.69766/XAEU5008`.
#
# The first steps are to define the plant location and to calculate solar position

Check failure on line 14 in docs/examples/agrivoltaics/plot_agrivoltaics_ground_irradiance.py

View workflow job for this annotation

GitHub Actions / flake8-linter

E501 line too long (82 > 79 characters)
# and clearsky irradiance for a single day as an example.
#
# .. figure:: ../../_images/agrivoltaics_system.jpg
# :align: center
# :width: 75%
# :alt: Photo of an agriPV system
#
# Photo of an agriPV system.
# *Source: Adam R. Jensen*

import pvlib
from pvlib.tools import cosd # we'll want this later
import pandas as pd
import matplotlib.pyplot as plt

location = pvlib.location.Location(latitude=55, longitude=10)

times = pd.date_range('2020-06-28', periods=24*60, freq='1min', tz='UTC')

solpos = location.get_solarposition(times)

clearsky = location.get_clearsky(times, model='ineichen')

# %%
# Next, we need to define the plant layout:

height = 3 # [m] height of torque above ground
pitch = 12 # [m] row spacing
row_width = 2 * 2 # [m] two modules in portrait, each 2 m long
gcr = row_width / pitch # ground coverage ratio [unitless]
axis_azimuth = 0 # [degrees] north-south tracking axis
max_angle = 50 # [degrees] maximum rotation angle

# %%
# Before running the infinite sheds model, we need to know the orientation
# of the trackers. For a single-axis tracker, this can be calculated as:

tracking_orientations = pvlib.tracking.singleaxis(
apparent_zenith=solpos['apparent_zenith'],
apparent_azimuth=solpos['azimuth'],
axis_azimuth=axis_azimuth,
max_angle=max_angle,
backtrack=True,
gcr=gcr,
)

# %%
# For agrivPV systems, the local albedo is dependent on crop growth and thus
# changes throughout the seasons. In this example, we only simulate one
# day and thus use a constant value. Similarly, we will assume a constant
# air temperature to avoid getting external data. Both albedo and air temperature

Check failure on line 65 in docs/examples/agrivoltaics/plot_agrivoltaics_ground_irradiance.py

View workflow job for this annotation

GitHub Actions / flake8-linter

E501 line too long (81 > 79 characters)
# could be defined as Series with the same index as used for the solar position
# calculations.

albedo = 0.25 # [unitless]
temp_air = 18 # [degrees C]

# %%
# Now, we are ready to calculate the front and rear-side irradiance using
# the pvlib infinite sheds model.

dni_extra = pvlib.irradiance.get_extra_radiation(times)

irradiance = pvlib.bifacial.infinite_sheds.get_irradiance(
surface_tilt=tracking_orientations['surface_tilt'],
surface_azimuth=tracking_orientations['surface_azimuth'],
solar_zenith=solpos['apparent_zenith'],
solar_azimuth=solpos['azimuth'],
gcr=gcr,
height=height,
pitch=pitch,
ghi=clearsky['ghi'],
dhi=clearsky['dhi'],
dni=clearsky['dni'],
albedo=albedo,
model='haydavies',
dni_extra=dni_extra,
bifaciality=0.8,
)

# %%
# Once the in-plane irradiance is known, we can estimate the PV array power.
# For simplicity, we use the PVWatts model:

N_tables = 108
modules_per_table = 14
pdc0_per_module = 380 # [W] STC rating
pdc0 = pdc0_per_module * modules_per_table * N_tables

gamma_pdc = -0.004 # [1/degrees C]

temp_cell = pvlib.temperature.faiman(
poa_global=irradiance['poa_global'],
temp_air=temp_air,
)

power_dc = pvlib.pvsystem.pvwatts_dc(
g_poa_effective=irradiance['poa_global'],
temp_cell=temp_cell,
pdc0=pdc0,
gamma_pdc=gamma_pdc)

power_dc.divide(1000).plot()
plt.ylabel('DC power [kW]')
plt.show()

# %%
# In addition to the power output of the PV array, we are also interested
# in how much irradiance reaches the crops under the array. In this case
# we calculate the average irradiance on the ground between two rows, using
# the infinite sheds utility functions.
#
# This consists of two parts. First we determine the diffuse irradiance on
# ground and second we calculate the fraction of the ground that is unshaded
# (i.e., receives DNI).

vf_ground_sky = pvlib.bifacial.utils.vf_ground_sky_2d_integ(
surface_tilt=tracking_orientations['surface_tilt'],
gcr=gcr,
height=height,
pitch=pitch,
)

unshaded_ground_fraction = pvlib.bifacial.utils._unshaded_ground_fraction(
surface_tilt=tracking_orientations['surface_tilt'],
surface_azimuth=tracking_orientations['surface_azimuth'],
solar_zenith=solpos['apparent_zenith'],
solar_azimuth=solpos['azimuth'],
gcr=gcr,
)

crop_avg_irradiance = (unshaded_ground_fraction * clearsky['dni']
* cosd(solpos['apparent_zenith'])
+ vf_ground_sky * clearsky['dhi'])

fig, ax = plt.subplots()
clearsky['ghi'].plot(ax=ax, label='Horizontal irradiance above panels')
crop_avg_irradiance.plot(ax=ax, label='Horizontal irradiance at crop level')
ax.legend(loc='upper center')
ax.set_ylabel('Irradiance [W/m$^2$]')
ax.set_ylim(-10, 1050)
plt.show()
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions docs/sphinx/source/whatsnew/v0.12.1.rst
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ Documentation
:py:func:`~pvlib.pvsystem.sapm` (:issue:`2392`, :pull:`2435`)
* Update references in :py:func`~pvlib.irradiance.get_extra_radiation`
(:issue:`2333`, :pull:`2347`)
* Add gallery example on calculating irradiance at crop level for agriPV systems.
(:pull:`2459`)

Requirements
~~~~~~~~~~~~
Expand Down
Loading