Skip to content

Commit 1901b20

Browse files
committed
Merge branch 'main' of https://github.com/pvlib/pvlib-python into sdm_convert
2 parents ae4346b + 52afdcf commit 1901b20

30 files changed

+1153
-824
lines changed

.github/workflows/publish.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
- name: Set up Python
2323
uses: actions/setup-python@v2
2424
with:
25-
python-version: 3.8
25+
python-version: 3.9
2626

2727
- name: Install build tools
2828
run: |

.github/workflows/pytest-remote-data.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,10 @@ jobs:
5656
strategy:
5757
fail-fast: false # don't cancel other matrix jobs when one fails
5858
matrix:
59-
python-version: [3.8, 3.9, "3.10", "3.11", "3.12"]
59+
python-version: [3.9, "3.10", "3.11", "3.12"]
6060
suffix: [''] # the alternative to "-min"
6161
include:
62-
- python-version: 3.8
62+
- python-version: 3.9
6363
suffix: -min
6464

6565
runs-on: ubuntu-latest
@@ -103,7 +103,7 @@ jobs:
103103
run: pytest pvlib/tests/iotools --cov=./ --cov-report=xml --remote-data
104104

105105
- name: Upload coverage to Codecov
106-
if: matrix.python-version == 3.8 && matrix.suffix == ''
106+
if: matrix.python-version == 3.9 && matrix.suffix == ''
107107
uses: codecov/codecov-action@v4
108108
with:
109109
fail_ci_if_error: true

.github/workflows/pytest.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ jobs:
1212
fail-fast: false # don't cancel other matrix jobs when one fails
1313
matrix:
1414
os: [ubuntu-latest, macos-latest, windows-latest]
15-
python-version: [3.8, 3.9, "3.10", "3.11", "3.12"]
15+
python-version: [3.9, "3.10", "3.11", "3.12"]
1616
environment-type: [conda, bare]
1717
suffix: [''] # placeholder as an alternative to "-min"
1818
include:
1919
- os: ubuntu-latest
20-
python-version: 3.8
20+
python-version: 3.9
2121
environment-type: conda
2222
suffix: -min
2323
exclude:
@@ -82,7 +82,7 @@ jobs:
8282
pytest pvlib --cov=./ --cov-report=xml --ignore=pvlib/tests/iotools
8383
8484
- name: Upload coverage to Codecov
85-
if: matrix.python-version == 3.8 && matrix.suffix == '' && matrix.os == 'ubuntu-latest' && matrix.environment-type == 'conda'
85+
if: matrix.python-version == 3.9 && matrix.suffix == '' && matrix.os == 'ubuntu-latest' && matrix.environment-type == 'conda'
8686
uses: codecov/codecov-action@v4
8787
with:
8888
fail_ci_if_error: true

benchmarks/asv.conf.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,19 +113,19 @@
113113
"include": [
114114
// minimum supported versions
115115
{
116-
"python": "3.8",
116+
"python": "3.9",
117117
"build": "",
118-
"numpy": "1.17.5",
118+
"numpy": "1.19.5",
119119
"pandas": "1.3.0",
120120
"scipy": "1.6.0",
121121
// Note: these don't have a minimum in setup.py
122122
"h5py": "3.1.0",
123-
"ephem": "3.7.7.0", // first version to support py 3.8
124-
"numba": "0.47.0", // first version to support py 3.8
123+
"ephem": "4.0.0.1", // first version to support py 3.9
124+
"numba": "0.53.0", // first version to support py 3.9
125125
},
126126
// latest versions available
127127
{
128-
"python": "3.8",
128+
"python": "3.9",
129129
"build": "",
130130
"numpy": "",
131131
"pandas": "",

ci/requirements-py3.8.yml

Lines changed: 0 additions & 28 deletions
This file was deleted.

ci/requirements-py3.8-min.yml renamed to ci/requirements-py3.9-min.yml

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,13 @@ dependencies:
88
- pytest-cov
99
- pytest-mock
1010
- pytest-timeout
11-
- python=3.8
11+
- python=3.9
1212
- pytz
1313
- requests
1414
- pip:
15-
- dataclasses
16-
- h5py==2.10.0 # chosen for compatibility with numpy 1.17.3 and py3.8
17-
- numpy==1.17.3
18-
- pandas==1.3.0
15+
- h5py==3.0.0
16+
- numpy==1.19.3
17+
- pandas==1.3.0 # min version of pvlib
1918
- scipy==1.6.0
2019
- pytest-rerunfailures # conda version is >3.6
2120
- pytest-remotedata # conda package is 0.3.0, needs > 0.3.1

docs/examples/irradiance-transposition/plot_seasonal_tilt.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ class SeasonalTiltMount(pvsystem.AbstractMount):
3232
surface_azimuth: float = 180.0
3333

3434
def get_orientation(self, solar_zenith, solar_azimuth):
35+
# note: determining tilt based on month may produce different
36+
# results depending on the time zone of the timestamps
3537
tilts = [self.monthly_tilts[m-1] for m in solar_zenith.index.month]
3638
return pd.DataFrame({
3739
'surface_tilt': tilts,
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
"""
2+
Average Photon Energy Calculation
3+
=================================
4+
5+
Calculation of the Average Photon Energy from SPECTRL2 output.
6+
"""
7+
8+
# %%
9+
# Introduction
10+
# ------------
11+
# This example demonstrates how to use the
12+
# :py:func:`~pvlib.spectrum.average_photon_energy` function to calculate the
13+
# Average Photon Energy (APE, :math:`\overline{E_\gamma}`) of spectral
14+
# irradiance distributions. This example uses spectral irradiance simulated
15+
# using :py:func:`~pvlib.spectrum.spectrl2`, but the same method is
16+
# applicable to spectral irradiance from any source.
17+
# More information on the SPECTRL2 model can be found in [1]_.
18+
# The APE parameter is a useful indicator of the overall shape of the solar
19+
# spectrum [2]_. Higher (lower) APE values indicate a blue (red) shift in the
20+
# spectrum and is one of a variety of such characterisation methods that is
21+
# used in the PV performance literature [3]_.
22+
#
23+
# To demonstrate this functionality, first we will simulate some spectra
24+
# using :py:func:`~pvlib.spectrum.spectrl2`. In this example, we will simulate
25+
# spectra following a similar method to that which is followed in the
26+
# :ref:`sphx_glr_gallery_spectrum_plot_spectrl2_fig51A.py` example, which
27+
# reproduces a figure from [4]_. The first step is to import the required
28+
# packages and define some basic system parameters and meteorological
29+
# conditions.
30+
31+
# %%
32+
import pandas as pd
33+
import matplotlib.pyplot as plt
34+
from scipy.integrate import trapezoid
35+
from pvlib import spectrum, solarposition, irradiance, atmosphere
36+
37+
lat, lon = 39.742, -105.18 # NREL SRRL location
38+
tilt = 25
39+
azimuth = 180 # south-facing system
40+
pressure = 81190 # at 1828 metres AMSL, roughly
41+
water_vapor_content = 0.5 # cm
42+
tau500 = 0.1
43+
ozone = 0.31 # atm-cm
44+
albedo = 0.2
45+
46+
times = pd.date_range('2023-01-01 08:00', freq='h', periods=9,
47+
tz='America/Denver')
48+
solpos = solarposition.get_solarposition(times, lat, lon)
49+
aoi = irradiance.aoi(tilt, azimuth, solpos.apparent_zenith, solpos.azimuth)
50+
51+
relative_airmass = atmosphere.get_relative_airmass(solpos.apparent_zenith,
52+
model='kastenyoung1989')
53+
54+
# %%
55+
# Spectral simulation
56+
# -------------------------
57+
# With all the necessary inputs now defined, we can model spectral irradiance
58+
# using :py:func:`pvlib.spectrum.spectrl2`. As we are calculating spectra for
59+
# more than one set of conditions, the function will return a dictionary
60+
# containing 2-D arrays for the spectral irradiance components and a 1-D array
61+
# of shape (122,) for wavelength. For each of the 2-D arrays, one dimension is
62+
# for wavelength in nm and one is for irradiance in Wm⁻²nm⁻¹.
63+
64+
spectra_components = spectrum.spectrl2(
65+
apparent_zenith=solpos.apparent_zenith,
66+
aoi=aoi,
67+
surface_tilt=tilt,
68+
ground_albedo=albedo,
69+
surface_pressure=pressure,
70+
relative_airmass=relative_airmass,
71+
precipitable_water=water_vapor_content,
72+
ozone=ozone,
73+
aerosol_turbidity_500nm=tau500,
74+
)
75+
76+
# %%
77+
# Visualising the spectral data
78+
# -----------------------------
79+
# Let's take a look at the spectral irradiance data simulated hourly for eight
80+
# hours on the first day of 2023.
81+
82+
plt.figure()
83+
plt.plot(spectra_components['wavelength'], spectra_components['poa_global'])
84+
plt.xlim(200, 2700)
85+
plt.ylim(0, 1.8)
86+
plt.ylabel(r"Spectral irradiance (Wm⁻²nm⁻¹)")
87+
plt.xlabel(r"Wavelength (nm)")
88+
time_labels = times.strftime("%H%M")
89+
labels = [
90+
f"{t}, {am_:0.02f}"
91+
for t, am_ in zip(time_labels, relative_airmass)
92+
]
93+
plt.legend(labels, title="Time, AM")
94+
plt.show()
95+
96+
# %%
97+
# Given the changing broadband irradiance throughout the day, it is not obvious
98+
# from inspection how the relative distribution of light changes as a function
99+
# of wavelength. We can normalise the spectral irradiance curves to visualise
100+
# this shift in the shape of the spectrum over the course of the day. In
101+
# this example, we normalise by dividing each spectral irradiance value by the
102+
# total broadband irradiance, which we calculate by integrating the entire
103+
# spectral irradiance distribution with respect to wavelength.
104+
105+
spectral_poa = spectra_components['poa_global']
106+
wavelength = spectra_components['wavelength']
107+
108+
broadband_irradiance = trapezoid(spectral_poa, wavelength, axis=0)
109+
110+
spectral_poa_normalised = spectral_poa / broadband_irradiance
111+
112+
# Plot the normalised spectra
113+
plt.figure()
114+
plt.plot(wavelength, spectral_poa_normalised)
115+
plt.xlim(200, 2700)
116+
plt.ylim(0, 0.0018)
117+
plt.ylabel(r"Normalised Irradiance (nm⁻¹)")
118+
plt.xlabel(r"Wavelength (nm)")
119+
time_labels = times.strftime("%H%M")
120+
labels = [
121+
f"{t}, {am_:0.02f}"
122+
for t, am_ in zip(time_labels, relative_airmass)
123+
]
124+
plt.legend(labels, title="Time, AM")
125+
plt.show()
126+
127+
# %%
128+
# We can now see from the normalised irradiance curves that at the start and
129+
# end of the day, the spectrum is red shifted, meaning there is a greater
130+
# proportion of longer wavelength radiation. Meanwhile, during the middle of
131+
# the day, there is a greater prevalence of shorter wavelength radiation — a
132+
# blue shifted spectrum.
133+
#
134+
# How can we quantify this shift? This is where the average photon energy comes
135+
# into play.
136+
137+
# %%
138+
# Calculating the average photon energy
139+
# -------------------------------------
140+
# To calculate the APE, first we must convert our output spectra from the
141+
# simulation into a compatible input for
142+
# :py:func:`pvlib.spectrum.average_photon_energy`. Since we have more than one
143+
# spectral irradiance distribution, a :py:class:`pandas.DataFrame` is
144+
# appropriate. We also need to set the column headers as wavelength, so each
145+
# row is a single spectral irradiance distribution. It is important to remember
146+
# here that the resulting APE values depend on the integration limits, i.e.
147+
# the wavelength range of the spectral irradiance input. APE values are only
148+
# comparable if calculated between the same integration limits. In this case,
149+
# our APE values are calculated between 300nm and 4000nm.
150+
151+
spectra = pd.DataFrame(spectral_poa).T # convert to dataframe and transpose
152+
spectra.index = time_labels # add time index
153+
spectra.columns = wavelength # add wavelength column headers
154+
155+
ape = spectrum.average_photon_energy(spectra)
156+
157+
# %%
158+
# We can update the normalised spectral irradiance plot to include the APE
159+
# value of each spectral irradiance distribution in the legend. Note that the
160+
# units of the APE are electronvolts (eV).
161+
162+
plt.figure()
163+
plt.plot(wavelength, spectral_poa_normalised)
164+
plt.xlim(200, 2700)
165+
plt.ylim(0, 0.0018)
166+
plt.ylabel(r"Normalised Irradiance (nm⁻¹)")
167+
plt.xlabel(r"Wavelength (nm)")
168+
time_labels = times.strftime("%H%M")
169+
labels = [
170+
f"{t}, {ape_:0.02f}"
171+
for t, ape_ in zip(time_labels, ape)
172+
]
173+
plt.legend(labels, title="Time, APE (eV)")
174+
plt.show()
175+
176+
# %%
177+
# As expected, the morning and evening spectra have a lower APE while a higher
178+
# APE is observed closer to the middle of the day. For reference, AM1.5 between
179+
# 300 and 4000 nm is 1.4501 eV. This indicates that the simulated spectra are
180+
# slightly red shifted with respect to the AM1.5 standard reference spectrum.
181+
182+
# %%
183+
# References
184+
# ----------
185+
# .. [1] Bird, R, and Riordan, C., 1984, "Simple solar spectral model for
186+
# direct and diffuse irradiance on horizontal and tilted planes at the
187+
# earth's surface for cloudless atmospheres", NREL Technical Report
188+
# TR-215-2436 :doi:`10.2172/5986936`
189+
# .. [2] Jardine, C., et al., 2002, January. Influence of spectral effects on
190+
# the performance of multijunction amorphous silicon cells. In Proc.
191+
# Photovoltaic in Europe Conference (pp. 1756-1759)
192+
# .. [3] Daxini, R., and Wu, Y., 2023. "Review of methods to account
193+
# for the solar spectral influence on photovoltaic device performance."
194+
# Energy 286 :doi:`10.1016/j.energy.2023.129461`
195+
# .. [4] Bird Simple Spectral Model: spectrl2_2.c
196+
# https://www.nrel.gov/grid/solar-resource/spectral.html
197+
# (Last accessed: 18/09/2024)

0 commit comments

Comments
 (0)