Skip to content

Commit 8700141

Browse files
committed
Merge remote-tracking branch 'upstream/main' into spectrum_reference
2 parents 4b4aac4 + 6dfeaf8 commit 8700141

70 files changed

Lines changed: 55509 additions & 9445 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/asv_check.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ jobs:
2323
- name: Install Python
2424
uses: actions/setup-python@v5
2525
with:
26-
python-version: '3.9'
26+
python-version: '3.12'
2727

2828
- name: Install asv
2929
run: pip install asv==0.6.4

.github/workflows/flake8.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ jobs:
77
steps:
88
- name: Checkout source
99
uses: actions/checkout@v4
10-
- name: Install Python 3.11
10+
- name: Install Python 3.12
1111
uses: actions/setup-python@v5
1212
with:
13-
python-version: '3.11'
13+
python-version: '3.12'
1414
- name: Install Flake8 5.0.4 linter
1515
run: pip install flake8==5.0.4 # use this version for --diff option
1616
- name: Setup Flake8 output matcher for PR annotations

.github/workflows/publish.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ jobs:
2222
- name: Set up Python
2323
uses: actions/setup-python@v5
2424
with:
25-
python-version: 3.9
25+
# Python version should be the minimum supported version
26+
python-version: "3.9"
2627

2728
- name: Install build tools
2829
run: |

.github/workflows/pytest.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,11 @@ jobs:
4040

4141
- name: Install Conda environment with Micromamba
4242
if: matrix.environment-type == 'conda'
43-
uses: mamba-org/setup-micromamba@v1
43+
uses: mamba-org/setup-micromamba@v2
4444
with:
4545
environment-file: ${{ env.REQUIREMENTS }}
46-
cache-downloads: true
46+
cache-downloads: false
47+
cache-environment: false
4748
create-args: >-
4849
python=${{ matrix.python-version }}
4950
condarc: |

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,17 @@ Documentation
6868
=============
6969

7070
Full documentation can be found at [readthedocs](http://pvlib-python.readthedocs.io/en/stable/),
71-
including an [FAQ](http://pvlib-python.readthedocs.io/en/stable/user_guide/faq.html) page.
71+
including an [FAQ](https://pvlib-python.readthedocs.io/en/stable/user_guide/extras/faq.html) page.
7272

7373
Installation
7474
============
7575

7676
pvlib-python releases may be installed using the ``pip`` and ``conda`` tools.
77-
Please see the [Installation page](https://pvlib-python.readthedocs.io/en/stable/user_guide/installation.html) of the documentation for complete instructions.
77+
```bash
78+
pip install pvlib
79+
conda install -c conda-forge pvlib
80+
```
81+
Please see the [Installation page](https://pvlib-python.readthedocs.io/en/stable/user_guide/getting_started/installation.html) of the documentation for complete instructions.
7882

7983

8084
Contributing
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
"""
2+
Agrivoltaic system modeling
3+
===========================
4+
5+
Irradiance at crop level between rows
6+
"""
7+
8+
# %%
9+
# This example demonstrates how to calculate power output for a bifacial
10+
# agriPV plant as well as calculating the irradiance at crop level
11+
# using pvlib's infinite sheds model.
12+
# For an overview of agrivPV concepts and performance, the reader
13+
# is referred to :doi:`10.69766/XAEU5008`.
14+
#
15+
# This gallery example is based on an actual AgriPV plant, namely
16+
# European Energy's `Flakkebjerg AgriPV site
17+
# <https://europeanenergy.com/2023/12/20/using-the-same-land-twice-at-european-\
18+
# energys-flakkebjerg-solar-park/>`_.
19+
#
20+
# The first steps are to define the plant location and to calculate solar
21+
# position and clearsky irradiance for a single day as an example.
22+
#
23+
# .. figure:: ../../_images/agrivoltaics_system.jpg
24+
# :align: center
25+
# :width: 75%
26+
# :alt: Photo of an agriPV system
27+
#
28+
# Photo of an agriPV system.
29+
# *Source: Adam R. Jensen*
30+
31+
import pvlib
32+
import pandas as pd
33+
from pvlib.tools import cosd
34+
import matplotlib.pyplot as plt
35+
36+
# sphinx_gallery_thumbnail_path = '_images/agrivoltaics_system.jpg'
37+
38+
location = pvlib.location.Location(latitude=55, longitude=10)
39+
40+
times = pd.date_range('2020-06-28', periods=24*60, freq='1min', tz='UTC')
41+
42+
solpos = location.get_solarposition(times)
43+
44+
clearsky = location.get_clearsky(times, model='ineichen')
45+
46+
# %%
47+
# Next, we need to define the plant layout:
48+
49+
height = 2.6 # [m] height of torque above ground
50+
pitch = 12 # [m] row spacing
51+
row_width = 2 * 2.384 # [m] two modules in portrait, each 2 m long
52+
gcr = row_width / pitch # ground coverage ratio [unitless]
53+
axis_azimuth = 0 # [degrees] north-south tracking axis
54+
max_angle = 55 # [degrees] maximum rotation angle
55+
56+
# %%
57+
# Before running the infinite sheds model, we need to know the orientation
58+
# of the trackers. For a single-axis tracker, this can be calculated as:
59+
60+
tracking_orientations = pvlib.tracking.singleaxis(
61+
apparent_zenith=solpos['apparent_zenith'],
62+
solar_azimuth=solpos['azimuth'],
63+
axis_azimuth=axis_azimuth,
64+
max_angle=max_angle,
65+
backtrack=True,
66+
gcr=gcr,
67+
)
68+
69+
# %%
70+
# For agrivPV systems, the local albedo is dependent on crop growth and thus
71+
# changes throughout the seasons. In this example, we only simulate one
72+
# day and thus use a constant value. Similarly, we will assume a constant
73+
# air temperature to avoid getting external data. Both albedo and air
74+
# temperature could be defined as Series with the same index as used for the
75+
# solar position calculations.
76+
77+
albedo = 0.20 # [unitless]
78+
temp_air = 18 # [degrees C]
79+
80+
# %%
81+
# Now, we are ready to calculate the front and rear-side irradiance using
82+
# the pvlib infinite sheds model.
83+
84+
dni_extra = pvlib.irradiance.get_extra_radiation(times)
85+
86+
irradiance = pvlib.bifacial.infinite_sheds.get_irradiance(
87+
surface_tilt=tracking_orientations['surface_tilt'],
88+
surface_azimuth=tracking_orientations['surface_azimuth'],
89+
solar_zenith=solpos['apparent_zenith'],
90+
solar_azimuth=solpos['azimuth'],
91+
gcr=gcr,
92+
height=height,
93+
pitch=pitch,
94+
ghi=clearsky['ghi'],
95+
dhi=clearsky['dhi'],
96+
dni=clearsky['dni'],
97+
albedo=albedo,
98+
model='haydavies',
99+
dni_extra=dni_extra,
100+
bifaciality=0.7, # [unitless] rear-side power relative to front-side
101+
)
102+
103+
# %%
104+
# Once the in-plane irradiance is known, we can estimate the PV array power.
105+
# For simplicity, we use the PVWatts model:
106+
107+
N_modules = 1512 # [unitless] Number of modules
108+
pdc0_per_module = 660 # [W] STC rating
109+
pdc0 = pdc0_per_module * N_modules
110+
111+
gamma_pdc = -0.004 # [1/degrees C]
112+
113+
temp_cell = pvlib.temperature.faiman(
114+
poa_global=irradiance['poa_global'],
115+
temp_air=temp_air,
116+
)
117+
118+
power_dc = pvlib.pvsystem.pvwatts_dc(
119+
effective_irradiance=irradiance['poa_global'],
120+
temp_cell=temp_cell,
121+
pdc0=pdc0,
122+
gamma_pdc=gamma_pdc)
123+
124+
power_dc.divide(1000).plot()
125+
plt.ylabel('DC power [kW]')
126+
plt.show()
127+
128+
# %%
129+
# In addition to the power output of the PV array, we are also interested
130+
# in how much irradiance reaches the crops under the array. In this case
131+
# we calculate the average irradiance on the ground between two rows, using
132+
# the infinite sheds utility functions.
133+
#
134+
# This consists of two parts. First we determine the diffuse irradiance on
135+
# ground and second we calculate the fraction of the ground that is unshaded
136+
# (i.e., receives DNI).
137+
138+
vf_ground_sky = pvlib.bifacial.utils.vf_ground_sky_2d_integ(
139+
surface_tilt=tracking_orientations['surface_tilt'],
140+
gcr=gcr,
141+
height=height,
142+
pitch=pitch,
143+
)
144+
145+
unshaded_ground_fraction = pvlib.bifacial.utils._unshaded_ground_fraction(
146+
surface_tilt=tracking_orientations['surface_tilt'],
147+
surface_azimuth=tracking_orientations['surface_azimuth'],
148+
solar_zenith=solpos['apparent_zenith'],
149+
solar_azimuth=solpos['azimuth'],
150+
gcr=gcr,
151+
)
152+
153+
crop_avg_irradiance = (unshaded_ground_fraction * clearsky['dni']
154+
* cosd(solpos['apparent_zenith'])
155+
+ vf_ground_sky * clearsky['dhi'])
156+
157+
fig, ax = plt.subplots()
158+
clearsky['ghi'].plot(ax=ax, label='Horizontal irradiance above panels')
159+
crop_avg_irradiance.plot(ax=ax, label='Horizontal irradiance at crop level')
160+
ax.legend(loc='upper center')
161+
ax.set_ylabel('Irradiance [W/m$^2$]')
162+
ax.set_ylim(-10, 1050)
163+
plt.show()

docs/examples/shading/plot_martinez_shade_loss.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@
9090

9191
tracking_result = pvlib.tracking.singleaxis(
9292
apparent_zenith=solar_apparent_zenith,
93-
apparent_azimuth=solar_azimuth,
93+
solar_azimuth=solar_azimuth,
9494
axis_tilt=axis_tilt,
9595
axis_azimuth=axis_azimuth,
9696
max_angle=(-90 + cross_axis_tilt, 90 + cross_axis_tilt), # (min, max)

docs/examples/solar-tracking/plot_single_axis_tracking.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333

3434
truetracking_angles = tracking.singleaxis(
3535
apparent_zenith=solpos['apparent_zenith'],
36-
apparent_azimuth=solpos['azimuth'],
36+
solar_azimuth=solpos['azimuth'],
3737
axis_tilt=0,
3838
axis_azimuth=180,
3939
max_angle=90,
@@ -61,7 +61,7 @@
6161
for gcr in [0.2, 0.4, 0.6]:
6262
backtracking_angles = tracking.singleaxis(
6363
apparent_zenith=solpos['apparent_zenith'],
64-
apparent_azimuth=solpos['azimuth'],
64+
solar_azimuth=solpos['azimuth'],
6565
axis_tilt=0,
6666
axis_azimuth=180,
6767
max_angle=90,

docs/examples/solar-tracking/plot_single_axis_tracking_on_sloped_terrain.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@
100100
for cross_axis_tilt in [0, 5, 10]:
101101
tracker_data = tracking.singleaxis(
102102
apparent_zenith=solpos['apparent_zenith'],
103-
apparent_azimuth=solpos['azimuth'],
103+
solar_azimuth=solpos['azimuth'],
104104
axis_tilt=0, # flat because the axis is perpendicular to the slope
105105
axis_azimuth=180, # N-S axis, azimuth facing south
106106
max_angle=90,
@@ -156,7 +156,7 @@
156156

157157
tracker_data = tracking.singleaxis(
158158
apparent_zenith=solpos['apparent_zenith'],
159-
apparent_azimuth=solpos['azimuth'],
159+
solar_azimuth=solpos['azimuth'],
160160
axis_tilt=axis_tilt, # no longer flat because the terrain imparts a tilt
161161
axis_azimuth=axis_azimuth,
162162
max_angle=90,

docs/examples/system-models/plot_oedi_9068.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@
165165
# module fraction and returns the average irradiance over the total module
166166
# surface.
167167

168-
solar_position = location.get_solarposition(psm3.index, latitude, longitude)
168+
solar_position = location.get_solarposition(psm3.index)
169169
tracker_angles = mount.get_orientation(
170170
solar_position['apparent_zenith'],
171171
solar_position['azimuth']

0 commit comments

Comments
 (0)