Skip to content

Commit 9973f50

Browse files
committed
Import plot & compare script from my PR of MR model at PVLIB's
pvlib/pvlib-python#1658
1 parent 2844651 commit 9973f50

File tree

1 file changed

+260
-0
lines changed

1 file changed

+260
-0
lines changed
Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
"""
2+
N. Martin & J. M. Ruiz Spectral Mismatch Modifier
3+
=================================================
4+
5+
How to use this correction factor to adjust the POA global irradiance.
6+
"""
7+
8+
# %%
9+
# Effectiveness of a material to convert incident sunlight to current depends
10+
# on the incident light wavelength. During the day, the spectral distribution
11+
# of the incident irradiance varies from the standard testing spectra,
12+
# introducing a small difference between the expected and the real output.
13+
# In [1]_, N. Martín and J. M. Ruiz propose 3 mismatch factors, one for each
14+
# irradiance component. These mismatch modifiers are calculated with the help
15+
# of the airmass, the clearness index and three experimental fitting
16+
# parameters. In the same paper, these parameters have been obtained for m-Si,
17+
# p-Si and a-Si modules.
18+
# With :py:func:`pvlib.spectrum.martin_ruiz` we are able to make use of these
19+
# already computed values or provide ours.
20+
#
21+
# References
22+
# ----------
23+
# .. [1] Martín, N. and Ruiz, J.M. (1999), A new method for the spectral
24+
# characterisation of PV modules. Prog. Photovolt: Res. Appl., 7: 299-310.
25+
# :doi:`10.1002/(SICI)1099-159X(199907/08)7:4<299::AID-PIP260>3.0.CO;2-0`
26+
#
27+
# Calculating the incident and modified global irradiance
28+
# -------------------------------------------------------
29+
#
30+
# Mismatch modifiers are applied to the irradiance components, so first
31+
# step is to get them. We define an hypothetical POA surface and use a TMY to
32+
# compute sky diffuse, ground reflected and direct irradiances.
33+
34+
import os
35+
36+
import matplotlib.pyplot as plt
37+
import pvlib
38+
from scipy import stats
39+
import pandas as pd
40+
41+
surface_tilt = 40
42+
surface_azimuth = 180 # Pointing South
43+
44+
# Get TMY data & create location
45+
datapath = os.path.join(
46+
pvlib.__path__[0], "data", "tmy_45.000_8.000_2005_2016.csv"
47+
)
48+
pvgis_data, _, metadata, _ = pvlib.iotools.read_pvgis_tmy(
49+
datapath, map_variables=True
50+
)
51+
site = pvlib.location.Location(
52+
metadata["latitude"], metadata["longitude"], altitude=metadata["elevation"]
53+
)
54+
55+
# Coerce a year: function above returns typical months of different years
56+
pvgis_data.index = [ts.replace(year=2022) for ts in pvgis_data.index]
57+
# Select days to show
58+
weather_data = pvgis_data["2022-09-03":"2022-09-06"]
59+
60+
# Then calculate all we need to get the irradiance components
61+
solar_pos = site.get_solarposition(weather_data.index)
62+
63+
extra_rad = pvlib.irradiance.get_extra_radiation(weather_data.index)
64+
65+
poa_sky_diffuse = pvlib.irradiance.haydavies(
66+
surface_tilt,
67+
surface_azimuth,
68+
weather_data["dhi"],
69+
weather_data["dni"],
70+
extra_rad,
71+
solar_pos["apparent_zenith"],
72+
solar_pos["azimuth"],
73+
)
74+
75+
poa_ground_diffuse = pvlib.irradiance.get_ground_diffuse(
76+
surface_tilt, weather_data["ghi"]
77+
)
78+
79+
aoi = pvlib.irradiance.aoi(
80+
surface_tilt,
81+
surface_azimuth,
82+
solar_pos["apparent_zenith"],
83+
solar_pos["azimuth"],
84+
)
85+
86+
# Get dataframe with all components and global (includes 'poa_direct')
87+
poa_irrad = pvlib.irradiance.poa_components(
88+
aoi, weather_data["dni"], poa_sky_diffuse, poa_ground_diffuse
89+
)
90+
91+
# Apply Martin & Ruiz IAM modifiers
92+
iam_direct = pvlib.iam.martin_ruiz(aoi)
93+
iam_sky_diffuse, iam_ground_diffuse = pvlib.iam.martin_ruiz_diffuse(
94+
surface_tilt
95+
)
96+
97+
poa_irrad["poa_direct"] = poa_irrad["poa_direct"] * iam_direct
98+
poa_irrad["poa_sky_diffuse"] = poa_irrad["poa_sky_diffuse"] * iam_sky_diffuse
99+
poa_irrad["poa_ground_diffuse"] = (
100+
poa_irrad["poa_ground_diffuse"] * iam_ground_diffuse
101+
)
102+
103+
# %%
104+
# Here come the modifiers. Let's calculate them with the airmass and clearness
105+
# index.
106+
# First, let's find the airmass and the clearness index.
107+
# Little caution: default values for this model were fitted obtaining the
108+
# airmass through the `'kasten1966'` method, which is not used by default.
109+
110+
airmass = site.get_airmass(solar_position=solar_pos, model="kasten1966")
111+
airmass_absolute = airmass["airmass_absolute"] # We only use absolute airmass
112+
clearness_index = pvlib.irradiance.clearness_index(
113+
weather_data["ghi"], solar_pos["zenith"], extra_rad
114+
)
115+
116+
# Get the spectral mismatch modifiers
117+
spectral_modifiers = pvlib.spectrum.martin_ruiz(
118+
clearness_index, airmass_absolute, module_type="monosi"
119+
)
120+
121+
# %%
122+
# And then we can find the 3 modified components of the POA irradiance
123+
# by means of a simple multiplication.
124+
# Note, however, that this does not modify ``poa_global`` nor
125+
# ``poa_diffuse``, so we should update the dataframe afterwards.
126+
127+
poa_irrad_modified = poa_irrad * spectral_modifiers
128+
# Line above is equivalent to:
129+
# poa_irrad_modified = pd.DataFrame()
130+
# for component in ('poa_direct', 'poa_sky_diffuse', 'poa_ground_diffuse'):
131+
# poa_irrad_modified[component] = (poa_irrad[component]
132+
# * spectral_modifiers[component])
133+
134+
# We want global modified irradiance
135+
poa_irrad_modified["poa_global"] = (
136+
poa_irrad_modified["poa_direct"]
137+
+ poa_irrad_modified["poa_sky_diffuse"]
138+
+ poa_irrad_modified["poa_ground_diffuse"]
139+
)
140+
# Don't forget to update `'poa_diffuse'` if you want to use it
141+
# poa_irrad_modified['poa_diffuse'] = \
142+
# (poa_irrad_modified['poa_sky_diffuse']
143+
# + poa_irrad_modified['poa_ground_diffuse'])
144+
145+
# %%
146+
# Finally, let's plot the incident vs modified global irradiance, and their
147+
# difference.
148+
149+
poa_irrad_global_diff = (
150+
poa_irrad["poa_global"] - poa_irrad_modified["poa_global"]
151+
)
152+
plt.figure()
153+
datetimes = poa_irrad.index # common to poa_irrad_*
154+
plt.plot(datetimes, poa_irrad["poa_global"].to_numpy())
155+
plt.plot(datetimes, poa_irrad_modified["poa_global"].to_numpy())
156+
plt.plot(datetimes, poa_irrad_global_diff.to_numpy())
157+
plt.legend(["Incident", "Modified", "Difference"])
158+
plt.ylabel("POA Global irradiance [W/m²]")
159+
plt.grid()
160+
plt.show()
161+
162+
# %%
163+
# Comparison against other models
164+
# -------------------------------
165+
# During the addition of this model, a question arose about its trustworthiness
166+
# so, in order to check the integrity of the implementation, we will
167+
# compare it against :py:func:`pvlib.spectrum.spectral_factor_sapm` and
168+
# :py:func:`pvlib.spectrum.spectral_factor_firstsolar`.
169+
# Former model needs the parameters that characterise a module, but which one?
170+
# We will take the mean of Sandia parameters `'A0', 'A1', 'A2', 'A3', 'A4'` for
171+
# the same material type.
172+
# On the other hand, :py:func:`~pvlib.atmosphere.first_solar` needs the
173+
# precipitable water. We assume the standard spectrum, `1.42 cm`.
174+
175+
# Retrieve modules and select the subset we want to work with the SAPM model
176+
module_type = "mc-Si" # Equivalent to monosi
177+
sandia_modules = pvlib.pvsystem.retrieve_sam(name="SandiaMod")
178+
modules_subset = sandia_modules.loc[
179+
:, sandia_modules.loc["Material"] == module_type
180+
]
181+
182+
# Define typical module and get the means of the A0 to A4 parameters
183+
modules_aggregated = pd.DataFrame(index=("mean", "std"))
184+
for param in ("A0", "A1", "A2", "A3", "A4"):
185+
result, _, _ = stats.mvsdist(modules_subset.loc[param])
186+
modules_aggregated[param] = result.mean(), result.std()
187+
188+
# Check if 'mean' is a representative value with help of 'std' just in case
189+
print(modules_aggregated)
190+
191+
# Then apply the SAPM model and calculate introduced difference
192+
modifier_sapm_f1 = pvlib.spectrum.spectral_factor_sapm(
193+
airmass_absolute, modules_aggregated.loc["mean"]
194+
)
195+
poa_irrad_sapm_modified = poa_irrad["poa_global"] * modifier_sapm_f1
196+
poa_irrad_sapm_difference = poa_irrad["poa_global"] - poa_irrad_sapm_modified
197+
198+
# spectrum.spectral_factor_firstsolar model
199+
first_solar_pw = 1.42 # Default for AM1.5 spectrum
200+
modifier_first_solar = pvlib.spectrum.spectral_factor_firstsolar(
201+
first_solar_pw, airmass_absolute, module_type="monosi"
202+
)
203+
poa_irrad_first_solar_mod = poa_irrad["poa_global"] * modifier_first_solar
204+
poa_irrad_first_solar_diff = (
205+
poa_irrad["poa_global"] - poa_irrad_first_solar_mod
206+
)
207+
208+
# %%
209+
# Plot global irradiance difference over time
210+
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
211+
datetimes = poa_irrad_global_diff.index # common to poa_irrad_*_diff*
212+
plt.figure()
213+
plt.plot(
214+
datetimes, poa_irrad_global_diff.to_numpy(), label="spectrum.martin_ruiz"
215+
)
216+
plt.plot(
217+
datetimes,
218+
poa_irrad_sapm_difference.to_numpy(),
219+
label="spectrum.spectral_factor_firstsolar",
220+
)
221+
plt.plot(
222+
datetimes,
223+
poa_irrad_first_solar_diff.to_numpy(),
224+
label="pvlib.spectrum.spectral_factor_sapm",
225+
)
226+
plt.legend()
227+
plt.title("Introduced difference comparison of different models")
228+
plt.ylabel("POA Global Irradiance Difference [W/m²]")
229+
plt.grid()
230+
plt.show()
231+
232+
# %%
233+
# Plot modifier vs absolute airmass
234+
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
235+
ama = airmass_absolute.to_numpy()
236+
# spectrum.martin_ruiz has 3 modifiers, so we only calculate one as
237+
# M = S_eff / S_incident that takes into account the global effect
238+
martin_ruiz_agg_modifier = (
239+
poa_irrad_modified["poa_global"] / poa_irrad["poa_global"]
240+
)
241+
plt.figure()
242+
plt.scatter(
243+
ama, martin_ruiz_agg_modifier.to_numpy(), label="spectrum.martin_ruiz"
244+
)
245+
plt.scatter(
246+
ama,
247+
modifier_sapm_f1.to_numpy(),
248+
label="pvlib.spectrum.spectral_factor_sapm",
249+
)
250+
plt.scatter(
251+
ama,
252+
modifier_first_solar.to_numpy(),
253+
label="spectrum.spectral_factor_firstsolar",
254+
)
255+
plt.legend()
256+
plt.title("Introduced difference comparison of different models")
257+
plt.xlabel("Absolute airmass")
258+
plt.ylabel(r"Modifier $M = \frac{S_{effective}}{S_{incident}}$")
259+
plt.grid()
260+
plt.show()

0 commit comments

Comments
 (0)