pvlib 0.11.0a1__py3-none-any.whl → 0.11.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pvlib/atmosphere.py +157 -1
- pvlib/bifacial/__init__.py +4 -4
- pvlib/bifacial/loss_models.py +163 -0
- pvlib/clearsky.py +18 -29
- pvlib/data/pvgis_tmy_meta.json +32 -93
- pvlib/data/pvgis_tmy_test.dat +8761 -8761
- pvlib/data/tmy_45.000_8.000_2005_2020.csv +8789 -0
- pvlib/data/tmy_45.000_8.000_2005_2020.epw +8768 -0
- pvlib/data/tmy_45.000_8.000_2005_2020.json +1 -0
- pvlib/data/tmy_45.000_8.000_2005_2020.txt +8761 -0
- pvlib/data/tmy_45.000_8.000_userhorizon.json +1 -1
- pvlib/data/variables_style_rules.csv +2 -1
- pvlib/iotools/pvgis.py +39 -3
- pvlib/irradiance.py +141 -120
- pvlib/location.py +5 -5
- pvlib/modelchain.py +1 -1
- pvlib/pvsystem.py +2 -2
- pvlib/shading.py +8 -8
- pvlib/singlediode.py +1 -1
- pvlib/solarposition.py +55 -50
- pvlib/spa.py +24 -22
- pvlib/spectrum/__init__.py +9 -4
- pvlib/spectrum/irradiance.py +272 -0
- pvlib/spectrum/mismatch.py +118 -508
- pvlib/spectrum/response.py +280 -0
- pvlib/spectrum/spectrl2.py +16 -16
- pvlib/tests/bifacial/test_losses_models.py +54 -0
- pvlib/tests/iotools/test_pvgis.py +57 -11
- pvlib/tests/spectrum/__init__.py +0 -0
- pvlib/tests/spectrum/conftest.py +40 -0
- pvlib/tests/spectrum/test_irradiance.py +138 -0
- pvlib/tests/{test_spectrum.py → spectrum/test_mismatch.py} +32 -306
- pvlib/tests/spectrum/test_response.py +124 -0
- pvlib/tests/spectrum/test_spectrl2.py +72 -0
- pvlib/tests/test_atmosphere.py +71 -0
- pvlib/tests/test_clearsky.py +37 -25
- pvlib/tests/test_irradiance.py +6 -6
- pvlib/tests/test_solarposition.py +84 -36
- pvlib/tests/test_spa.py +1 -1
- pvlib/tools.py +26 -2
- pvlib/tracking.py +53 -47
- {pvlib-0.11.0a1.dist-info → pvlib-0.11.1.dist-info}/METADATA +31 -29
- {pvlib-0.11.0a1.dist-info → pvlib-0.11.1.dist-info}/RECORD +47 -38
- {pvlib-0.11.0a1.dist-info → pvlib-0.11.1.dist-info}/WHEEL +1 -1
- pvlib/data/tmy_45.000_8.000_2005_2016.csv +0 -8789
- pvlib/data/tmy_45.000_8.000_2005_2016.epw +0 -8768
- pvlib/data/tmy_45.000_8.000_2005_2016.json +0 -1
- pvlib/data/tmy_45.000_8.000_2005_2016.txt +0 -8761
- {pvlib-0.11.0a1.dist-info → pvlib-0.11.1.dist-info}/AUTHORS.md +0 -0
- {pvlib-0.11.0a1.dist-info → pvlib-0.11.1.dist-info}/LICENSE +0 -0
- {pvlib-0.11.0a1.dist-info → pvlib-0.11.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The ``response`` module in the ``spectrum`` package provides functions for
|
|
3
|
+
spectral response and quantum efficiency calculations.
|
|
4
|
+
"""
|
|
5
|
+
from pvlib.tools import normalize_max2one
|
|
6
|
+
import numpy as np
|
|
7
|
+
import pandas as pd
|
|
8
|
+
import scipy.constants
|
|
9
|
+
from scipy.interpolate import interp1d
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
_PLANCK_BY_LIGHT_SPEED_OVER_ELEMENTAL_CHARGE_BY_BILLION = (
|
|
13
|
+
scipy.constants.speed_of_light
|
|
14
|
+
* scipy.constants.Planck
|
|
15
|
+
/ scipy.constants.elementary_charge
|
|
16
|
+
* 1e9
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_example_spectral_response(wavelength=None):
|
|
21
|
+
'''
|
|
22
|
+
Generate a generic smooth spectral response (SR) for tests and experiments.
|
|
23
|
+
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
wavelength: 1-D sequence of numeric, optional
|
|
27
|
+
Wavelengths at which spectral response values are generated.
|
|
28
|
+
By default ``wavelength`` is from 280 to 1200 in 5 nm intervals. [nm]
|
|
29
|
+
|
|
30
|
+
Returns
|
|
31
|
+
-------
|
|
32
|
+
spectral_response : pandas.Series
|
|
33
|
+
The relative spectral response indexed by ``wavelength`` in nm. [-]
|
|
34
|
+
|
|
35
|
+
Notes
|
|
36
|
+
-----
|
|
37
|
+
This spectral response is based on measurements taken on a c-Si cell.
|
|
38
|
+
A small number of points near the measured curve are used to define
|
|
39
|
+
a cubic spline having no undue oscillations, as shown in [1]_. The spline
|
|
40
|
+
can be interpolated at arbitrary wavelengths to produce a continuous,
|
|
41
|
+
smooth curve , which makes it suitable for experimenting with spectral
|
|
42
|
+
data of different resolutions.
|
|
43
|
+
|
|
44
|
+
References
|
|
45
|
+
----------
|
|
46
|
+
.. [1] Driesse, Anton, and Stein, Joshua. "Global Normal Spectral
|
|
47
|
+
Irradiance in Albuquerque: a One-Year Open Dataset for PV Research".
|
|
48
|
+
United States 2020. :doi:`10.2172/1814068`.
|
|
49
|
+
'''
|
|
50
|
+
# Contributed by Anton Driesse (@adriesse), PV Performance Labs. Aug. 2022
|
|
51
|
+
|
|
52
|
+
SR_DATA = np.array([[290, 0.00],
|
|
53
|
+
[350, 0.27],
|
|
54
|
+
[400, 0.37],
|
|
55
|
+
[500, 0.52],
|
|
56
|
+
[650, 0.71],
|
|
57
|
+
[800, 0.88],
|
|
58
|
+
[900, 0.97],
|
|
59
|
+
[950, 1.00],
|
|
60
|
+
[1000, 0.93],
|
|
61
|
+
[1050, 0.58],
|
|
62
|
+
[1100, 0.21],
|
|
63
|
+
[1150, 0.05],
|
|
64
|
+
[1190, 0.00]]).transpose()
|
|
65
|
+
|
|
66
|
+
if wavelength is None:
|
|
67
|
+
resolution = 5.0
|
|
68
|
+
wavelength = np.arange(280, 1200 + resolution, resolution)
|
|
69
|
+
|
|
70
|
+
interpolator = interp1d(SR_DATA[0], SR_DATA[1],
|
|
71
|
+
kind='cubic',
|
|
72
|
+
bounds_error=False,
|
|
73
|
+
fill_value=0.0,
|
|
74
|
+
copy=False,
|
|
75
|
+
assume_sorted=True)
|
|
76
|
+
|
|
77
|
+
sr = pd.Series(data=interpolator(wavelength), index=wavelength)
|
|
78
|
+
|
|
79
|
+
sr.index.name = 'wavelength'
|
|
80
|
+
sr.name = 'spectral_response'
|
|
81
|
+
|
|
82
|
+
return sr
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def sr_to_qe(sr, wavelength=None, normalize=False):
|
|
86
|
+
"""
|
|
87
|
+
Convert spectral responsivities to quantum efficiencies.
|
|
88
|
+
If ``wavelength`` is not provided, the spectral responsivity ``sr`` must be
|
|
89
|
+
a :py:class:`pandas.Series` or :py:class:`pandas.DataFrame`, with the
|
|
90
|
+
wavelengths in the index.
|
|
91
|
+
|
|
92
|
+
Provide wavelengths in nanometers, [nm].
|
|
93
|
+
|
|
94
|
+
Conversion is described in [1]_.
|
|
95
|
+
|
|
96
|
+
.. versionadded:: 0.11.0
|
|
97
|
+
|
|
98
|
+
Parameters
|
|
99
|
+
----------
|
|
100
|
+
sr : numeric, pandas.Series or pandas.DataFrame
|
|
101
|
+
Spectral response, [A/W].
|
|
102
|
+
Index must be the wavelength in nanometers, [nm].
|
|
103
|
+
|
|
104
|
+
wavelength : numeric, optional
|
|
105
|
+
Points where spectral response is measured, in nanometers, [nm].
|
|
106
|
+
|
|
107
|
+
normalize : bool, default False
|
|
108
|
+
If True, the quantum efficiency is normalized so that the maximum value
|
|
109
|
+
is 1.
|
|
110
|
+
For ``pandas.DataFrame``, normalization is done for each column.
|
|
111
|
+
For 2D arrays, normalization is done for each sub-array.
|
|
112
|
+
|
|
113
|
+
Returns
|
|
114
|
+
-------
|
|
115
|
+
quantum_efficiency : numeric, same type as ``sr``
|
|
116
|
+
Quantum efficiency, in the interval [0, 1].
|
|
117
|
+
|
|
118
|
+
Notes
|
|
119
|
+
-----
|
|
120
|
+
- If ``sr`` is of type ``pandas.Series`` or ``pandas.DataFrame``,
|
|
121
|
+
column names will remain unchanged in the returned object.
|
|
122
|
+
- If ``wavelength`` is provided it will be used independently of the
|
|
123
|
+
datatype of ``sr``.
|
|
124
|
+
|
|
125
|
+
Examples
|
|
126
|
+
--------
|
|
127
|
+
>>> import numpy as np
|
|
128
|
+
>>> import pandas as pd
|
|
129
|
+
>>> from pvlib import spectrum
|
|
130
|
+
>>> wavelengths = np.array([350, 550, 750])
|
|
131
|
+
>>> spectral_response = np.array([0.25, 0.40, 0.57])
|
|
132
|
+
>>> quantum_efficiency = spectrum.sr_to_qe(spectral_response, wavelengths)
|
|
133
|
+
>>> print(quantum_efficiency)
|
|
134
|
+
array([0.88560142, 0.90170326, 0.94227991])
|
|
135
|
+
|
|
136
|
+
>>> spectral_response_series = pd.Series(spectral_response, index=wavelengths, name="dataset")
|
|
137
|
+
>>> qe = spectrum.sr_to_qe(spectral_response_series)
|
|
138
|
+
>>> print(qe)
|
|
139
|
+
350 0.885601
|
|
140
|
+
550 0.901703
|
|
141
|
+
750 0.942280
|
|
142
|
+
Name: dataset, dtype: float64
|
|
143
|
+
|
|
144
|
+
>>> qe = spectrum.sr_to_qe(spectral_response_series, normalize=True)
|
|
145
|
+
>>> print(qe)
|
|
146
|
+
350 0.939850
|
|
147
|
+
550 0.956938
|
|
148
|
+
750 1.000000
|
|
149
|
+
Name: dataset, dtype: float64
|
|
150
|
+
|
|
151
|
+
References
|
|
152
|
+
----------
|
|
153
|
+
.. [1] “Spectral Response,” PV Performance Modeling Collaborative (PVPMC).
|
|
154
|
+
https://pvpmc.sandia.gov/modeling-guide/2-dc-module-iv/effective-irradiance/spectral-response/
|
|
155
|
+
.. [2] “Spectral Response | PVEducation,” www.pveducation.org.
|
|
156
|
+
https://www.pveducation.org/pvcdrom/solar-cell-operation/spectral-response
|
|
157
|
+
|
|
158
|
+
See Also
|
|
159
|
+
--------
|
|
160
|
+
pvlib.spectrum.qe_to_sr
|
|
161
|
+
""" # noqa: E501
|
|
162
|
+
if wavelength is None:
|
|
163
|
+
if hasattr(sr, "index"): # true for pandas objects
|
|
164
|
+
# use reference to index values instead of index alone so
|
|
165
|
+
# sr / wavelength returns a series with the same name
|
|
166
|
+
wavelength = sr.index.array
|
|
167
|
+
else:
|
|
168
|
+
raise TypeError(
|
|
169
|
+
"'sr' must have an '.index' attribute"
|
|
170
|
+
+ " or 'wavelength' must be provided"
|
|
171
|
+
)
|
|
172
|
+
quantum_efficiency = (
|
|
173
|
+
sr
|
|
174
|
+
/ wavelength
|
|
175
|
+
* _PLANCK_BY_LIGHT_SPEED_OVER_ELEMENTAL_CHARGE_BY_BILLION
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
if normalize:
|
|
179
|
+
quantum_efficiency = normalize_max2one(quantum_efficiency)
|
|
180
|
+
|
|
181
|
+
return quantum_efficiency
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def qe_to_sr(qe, wavelength=None, normalize=False):
|
|
185
|
+
"""
|
|
186
|
+
Convert quantum efficiencies to spectral responsivities.
|
|
187
|
+
If ``wavelength`` is not provided, the quantum efficiency ``qe`` must be
|
|
188
|
+
a :py:class:`pandas.Series` or :py:class:`pandas.DataFrame`, with the
|
|
189
|
+
wavelengths in the index.
|
|
190
|
+
|
|
191
|
+
Provide wavelengths in nanometers, [nm].
|
|
192
|
+
|
|
193
|
+
Conversion is described in [1]_.
|
|
194
|
+
|
|
195
|
+
.. versionadded:: 0.11.0
|
|
196
|
+
|
|
197
|
+
Parameters
|
|
198
|
+
----------
|
|
199
|
+
qe : numeric, pandas.Series or pandas.DataFrame
|
|
200
|
+
Quantum efficiency.
|
|
201
|
+
If pandas subtype, index must be the wavelength in nanometers, [nm].
|
|
202
|
+
|
|
203
|
+
wavelength : numeric, optional
|
|
204
|
+
Points where quantum efficiency is measured, in nanometers, [nm].
|
|
205
|
+
|
|
206
|
+
normalize : bool, default False
|
|
207
|
+
If True, the spectral response is normalized so that the maximum value
|
|
208
|
+
is 1.
|
|
209
|
+
For ``pandas.DataFrame``, normalization is done for each column.
|
|
210
|
+
For 2D arrays, normalization is done for each sub-array.
|
|
211
|
+
|
|
212
|
+
Returns
|
|
213
|
+
-------
|
|
214
|
+
spectral_response : numeric, same type as ``qe``
|
|
215
|
+
Spectral response, [A/W].
|
|
216
|
+
|
|
217
|
+
Notes
|
|
218
|
+
-----
|
|
219
|
+
- If ``qe`` is of type ``pandas.Series`` or ``pandas.DataFrame``,
|
|
220
|
+
column names will remain unchanged in the returned object.
|
|
221
|
+
- If ``wavelength`` is provided it will be used independently of the
|
|
222
|
+
datatype of ``qe``.
|
|
223
|
+
|
|
224
|
+
Examples
|
|
225
|
+
--------
|
|
226
|
+
>>> import numpy as np
|
|
227
|
+
>>> import pandas as pd
|
|
228
|
+
>>> from pvlib import spectrum
|
|
229
|
+
>>> wavelengths = np.array([350, 550, 750])
|
|
230
|
+
>>> quantum_efficiency = np.array([0.86, 0.90, 0.94])
|
|
231
|
+
>>> spectral_response = spectrum.qe_to_sr(quantum_efficiency, wavelengths)
|
|
232
|
+
>>> print(spectral_response)
|
|
233
|
+
array([0.24277287, 0.39924442, 0.56862085])
|
|
234
|
+
|
|
235
|
+
>>> quantum_efficiency_series = pd.Series(quantum_efficiency, index=wavelengths, name="dataset")
|
|
236
|
+
>>> sr = spectrum.qe_to_sr(quantum_efficiency_series)
|
|
237
|
+
>>> print(sr)
|
|
238
|
+
350 0.242773
|
|
239
|
+
550 0.399244
|
|
240
|
+
750 0.568621
|
|
241
|
+
Name: dataset, dtype: float64
|
|
242
|
+
|
|
243
|
+
>>> sr = spectrum.qe_to_sr(quantum_efficiency_series, normalize=True)
|
|
244
|
+
>>> print(sr)
|
|
245
|
+
350 0.426950
|
|
246
|
+
550 0.702128
|
|
247
|
+
750 1.000000
|
|
248
|
+
Name: dataset, dtype: float64
|
|
249
|
+
|
|
250
|
+
References
|
|
251
|
+
----------
|
|
252
|
+
.. [1] “Spectral Response,” PV Performance Modeling Collaborative (PVPMC).
|
|
253
|
+
https://pvpmc.sandia.gov/modeling-guide/2-dc-module-iv/effective-irradiance/spectral-response/
|
|
254
|
+
.. [2] “Spectral Response | PVEducation,” www.pveducation.org.
|
|
255
|
+
https://www.pveducation.org/pvcdrom/solar-cell-operation/spectral-response
|
|
256
|
+
|
|
257
|
+
See Also
|
|
258
|
+
--------
|
|
259
|
+
pvlib.spectrum.sr_to_qe
|
|
260
|
+
""" # noqa: E501
|
|
261
|
+
if wavelength is None:
|
|
262
|
+
if hasattr(qe, "index"): # true for pandas objects
|
|
263
|
+
# use reference to index values instead of index alone so
|
|
264
|
+
# sr / wavelength returns a series with the same name
|
|
265
|
+
wavelength = qe.index.array
|
|
266
|
+
else:
|
|
267
|
+
raise TypeError(
|
|
268
|
+
"'qe' must have an '.index' attribute"
|
|
269
|
+
+ " or 'wavelength' must be provided"
|
|
270
|
+
)
|
|
271
|
+
spectral_responsivity = (
|
|
272
|
+
qe
|
|
273
|
+
* wavelength
|
|
274
|
+
/ _PLANCK_BY_LIGHT_SPEED_OVER_ELEMENTAL_CHARGE_BY_BILLION
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
if normalize:
|
|
278
|
+
spectral_responsivity = normalize_max2one(spectral_responsivity)
|
|
279
|
+
|
|
280
|
+
return spectral_responsivity
|
pvlib/spectrum/spectrl2.py
CHANGED
|
@@ -181,7 +181,7 @@ def spectrl2(apparent_zenith, aoi, surface_tilt, ground_albedo,
|
|
|
181
181
|
(SPECTRL2).
|
|
182
182
|
|
|
183
183
|
The Bird Simple Spectral Model [1]_ produces terrestrial spectra between
|
|
184
|
-
300 and 4000 nm with a resolution of approximately 10 nm. Direct and
|
|
184
|
+
300 nm and 4000 nm with a resolution of approximately 10 nm. Direct and
|
|
185
185
|
diffuse spectral irradiance are modeled for horizontal and tilted surfaces
|
|
186
186
|
under cloudless skies. SPECTRL2 models radiative transmission, absorption,
|
|
187
187
|
and scattering due to atmospheric aerosol, water vapor, and ozone content.
|
|
@@ -189,31 +189,31 @@ def spectrl2(apparent_zenith, aoi, surface_tilt, ground_albedo,
|
|
|
189
189
|
Parameters
|
|
190
190
|
----------
|
|
191
191
|
apparent_zenith : numeric
|
|
192
|
-
Solar zenith angle [degrees]
|
|
192
|
+
Solar zenith angle. [degrees]
|
|
193
193
|
aoi : numeric
|
|
194
|
-
Angle of incidence of the solar vector on the panel [degrees]
|
|
194
|
+
Angle of incidence of the solar vector on the panel. [degrees]
|
|
195
195
|
surface_tilt : numeric
|
|
196
|
-
Panel tilt from horizontal [degrees]
|
|
196
|
+
Panel tilt from horizontal. [degrees]
|
|
197
197
|
ground_albedo : numeric
|
|
198
198
|
Albedo [0-1] of the ground surface. Can be provided as a scalar value
|
|
199
199
|
if albedo is not spectrally-dependent, or as a 122xN matrix where
|
|
200
200
|
the first dimension spans the wavelength range and the second spans
|
|
201
201
|
the number of simulations. [unitless]
|
|
202
202
|
surface_pressure : numeric
|
|
203
|
-
Surface pressure [Pa]
|
|
203
|
+
Surface pressure. [Pa]
|
|
204
204
|
relative_airmass : numeric
|
|
205
205
|
Relative airmass. The airmass model used in [1]_ is the `'kasten1966'`
|
|
206
206
|
model, while a later implementation by NREL uses the
|
|
207
207
|
`'kastenyoung1989'` model. [unitless]
|
|
208
208
|
precipitable_water : numeric
|
|
209
|
-
Atmospheric water vapor content [cm]
|
|
209
|
+
Atmospheric water vapor content. [cm]
|
|
210
210
|
ozone : numeric
|
|
211
|
-
Atmospheric ozone content [atm-cm]
|
|
211
|
+
Atmospheric ozone content. [atm-cm]
|
|
212
212
|
aerosol_turbidity_500nm : numeric
|
|
213
|
-
Aerosol turbidity at 500 nm [unitless]
|
|
213
|
+
Aerosol turbidity at 500 nm. [unitless]
|
|
214
214
|
dayofyear : numeric, optional
|
|
215
|
-
The day of year [1-365].
|
|
216
|
-
not a pandas
|
|
215
|
+
The day of year [1-365]. Must be provided if ``apparent_zenith`` is
|
|
216
|
+
not a ``pandas.Series``.
|
|
217
217
|
scattering_albedo_400nm : numeric, default 0.945
|
|
218
218
|
Aerosol single scattering albedo at 400nm. The default value of 0.945
|
|
219
219
|
is suggested in [1]_ for a rural aerosol model. [unitless]
|
|
@@ -223,15 +223,15 @@ def spectrl2(apparent_zenith, aoi, surface_tilt, ground_albedo,
|
|
|
223
223
|
wavelength_variation_factor : numeric, default 0.095
|
|
224
224
|
Wavelength variation factor [unitless]
|
|
225
225
|
aerosol_asymmetry_factor : numeric, default 0.65
|
|
226
|
-
Aerosol asymmetry factor (mean cosine of scattering angle) [unitless]
|
|
226
|
+
Aerosol asymmetry factor (mean cosine of scattering angle). [unitless]
|
|
227
227
|
|
|
228
228
|
Returns
|
|
229
229
|
-------
|
|
230
|
-
|
|
230
|
+
spectra_components : dict
|
|
231
231
|
A dict of arrays. With the exception of `wavelength`, which has length
|
|
232
232
|
122, each array has shape (122, N) where N is the length of the
|
|
233
233
|
input ``apparent_zenith``. All values are spectral irradiance
|
|
234
|
-
with units
|
|
234
|
+
with units Wm⁻²nm⁻¹, except for `wavelength`, which is in nanometers.
|
|
235
235
|
|
|
236
236
|
* wavelength
|
|
237
237
|
* dni_extra
|
|
@@ -267,7 +267,7 @@ def spectrl2(apparent_zenith, aoi, surface_tilt, ground_albedo,
|
|
|
267
267
|
|
|
268
268
|
References
|
|
269
269
|
----------
|
|
270
|
-
.. [1] Bird, R
|
|
270
|
+
.. [1] Bird, R., and Riordan, C., 1984, "Simple solar spectral model for
|
|
271
271
|
direct and diffuse irradiance on horizontal and tilted planes at the
|
|
272
272
|
earth's surface for cloudless atmospheres", NREL Technical Report
|
|
273
273
|
TR-215-2436 :doi:`10.2172/5986936`.
|
|
@@ -288,7 +288,7 @@ def spectrl2(apparent_zenith, aoi, surface_tilt, ground_albedo,
|
|
|
288
288
|
aerosol_turbidity_500nm, scattering_albedo_400nm, alpha,
|
|
289
289
|
wavelength_variation_factor, aerosol_asymmetry_factor]))
|
|
290
290
|
|
|
291
|
-
dayofyear = original_index.
|
|
291
|
+
dayofyear = pvlib.tools._pandas_to_doy(original_index).values
|
|
292
292
|
|
|
293
293
|
if not is_pandas and dayofyear is None:
|
|
294
294
|
raise ValueError('dayofyear must be specified if not using pandas '
|
|
@@ -363,7 +363,7 @@ def spectrl2(apparent_zenith, aoi, surface_tilt, ground_albedo,
|
|
|
363
363
|
|
|
364
364
|
# calculate spectral irradiance on a tilted surface, Eq 3-18
|
|
365
365
|
# Note: clipping cosd(aoi) to >=0 is not in the reference, but is necessary
|
|
366
|
-
# to prevent
|
|
366
|
+
# to prevent negative values when the sun is behind the plane of array.
|
|
367
367
|
# The same constraint is applied in irradiance.haydavies when not
|
|
368
368
|
# supplying `projection_ratio`.
|
|
369
369
|
aoi_projection_nn = np.maximum(cosd(aoi), 0) # GH 1348
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from pvlib import bifacial
|
|
2
|
+
|
|
3
|
+
import pandas as pd
|
|
4
|
+
import numpy as np
|
|
5
|
+
from numpy.testing import assert_allclose
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_power_mismatch_deline():
|
|
9
|
+
"""tests bifacial.power_mismatch_deline"""
|
|
10
|
+
premise_rmads = np.array([0.0, 0.05, 0.1, 0.15, 0.2, 0.25])
|
|
11
|
+
# test default model is for fixed tilt
|
|
12
|
+
expected_ft_mms = np.array([0.0, 0.0151, 0.0462, 0.0933, 0.1564, 0.2355])
|
|
13
|
+
result_def_mms = bifacial.power_mismatch_deline(premise_rmads)
|
|
14
|
+
assert_allclose(result_def_mms, expected_ft_mms, atol=1e-5)
|
|
15
|
+
assert np.all(np.diff(result_def_mms) > 0) # higher RMADs => higher losses
|
|
16
|
+
|
|
17
|
+
# test custom coefficients, set model to 1+1*RMAD
|
|
18
|
+
# as Polynomial class
|
|
19
|
+
polynomial = np.polynomial.Polynomial([1, 1, 0])
|
|
20
|
+
result_custom_mms = bifacial.power_mismatch_deline(
|
|
21
|
+
premise_rmads, coefficients=polynomial
|
|
22
|
+
)
|
|
23
|
+
assert_allclose(result_custom_mms, 1 + premise_rmads)
|
|
24
|
+
# as list
|
|
25
|
+
result_custom_mms = bifacial.power_mismatch_deline(
|
|
26
|
+
premise_rmads, coefficients=[1, 1, 0]
|
|
27
|
+
)
|
|
28
|
+
assert_allclose(result_custom_mms, 1 + premise_rmads)
|
|
29
|
+
|
|
30
|
+
# test datatypes IO with Series
|
|
31
|
+
result_mms = bifacial.power_mismatch_deline(pd.Series(premise_rmads))
|
|
32
|
+
assert isinstance(result_mms, pd.Series)
|
|
33
|
+
|
|
34
|
+
# test fill_factor, fill_factor_reference
|
|
35
|
+
# default model + default fill_factor_reference
|
|
36
|
+
ff_ref_default = 0.79
|
|
37
|
+
ff_of_interest = 0.65
|
|
38
|
+
result_mms = bifacial.power_mismatch_deline(
|
|
39
|
+
premise_rmads, fill_factor=ff_of_interest
|
|
40
|
+
)
|
|
41
|
+
assert_allclose(
|
|
42
|
+
result_mms,
|
|
43
|
+
expected_ft_mms * ff_of_interest / ff_ref_default,
|
|
44
|
+
atol=1e-5,
|
|
45
|
+
)
|
|
46
|
+
# default model + custom fill_factor_reference
|
|
47
|
+
ff_of_interest = 0.65
|
|
48
|
+
ff_ref = 0.75
|
|
49
|
+
result_mms = bifacial.power_mismatch_deline(
|
|
50
|
+
premise_rmads, fill_factor=ff_of_interest, fill_factor_reference=ff_ref
|
|
51
|
+
)
|
|
52
|
+
assert_allclose(
|
|
53
|
+
result_mms, expected_ft_mms * ff_of_interest / ff_ref, atol=1e-5
|
|
54
|
+
)
|
|
@@ -11,8 +11,7 @@ from pvlib.iotools import get_pvgis_tmy, read_pvgis_tmy
|
|
|
11
11
|
from pvlib.iotools import get_pvgis_hourly, read_pvgis_hourly
|
|
12
12
|
from pvlib.iotools import get_pvgis_horizon
|
|
13
13
|
from ..conftest import (DATA_DIR, RERUNS, RERUNS_DELAY, assert_frame_equal,
|
|
14
|
-
|
|
15
|
-
from pvlib._deprecation import pvlibDeprecationWarning
|
|
14
|
+
assert_series_equal)
|
|
16
15
|
|
|
17
16
|
|
|
18
17
|
# PVGIS Hourly tests
|
|
@@ -316,7 +315,7 @@ def userhorizon_expected():
|
|
|
316
315
|
@pytest.fixture
|
|
317
316
|
def month_year_expected():
|
|
318
317
|
return [
|
|
319
|
-
|
|
318
|
+
2014, 2011, 2008, 2011, 2009, 2011, 2020, 2006, 2006, 2013, 2007, 2018]
|
|
320
319
|
|
|
321
320
|
|
|
322
321
|
@pytest.fixture
|
|
@@ -324,10 +323,10 @@ def inputs_expected():
|
|
|
324
323
|
return {
|
|
325
324
|
'location': {'latitude': 45.0, 'longitude': 8.0, 'elevation': 250.0},
|
|
326
325
|
'meteo_data': {
|
|
327
|
-
'radiation_db': 'PVGIS-
|
|
328
|
-
'meteo_db': '
|
|
326
|
+
'radiation_db': 'PVGIS-SARAH2',
|
|
327
|
+
'meteo_db': 'ERA5',
|
|
329
328
|
'year_min': 2005,
|
|
330
|
-
'year_max':
|
|
329
|
+
'year_max': 2020,
|
|
331
330
|
'use_horizon': True,
|
|
332
331
|
'horizon_db': 'DEM-calculated'}}
|
|
333
332
|
|
|
@@ -435,6 +434,53 @@ def _compare_pvgis_tmy_basic(expected, meta_expected, pvgis_data):
|
|
|
435
434
|
assert np.allclose(data[outvar], expected[outvar])
|
|
436
435
|
|
|
437
436
|
|
|
437
|
+
@pytest.mark.remote_data
|
|
438
|
+
@pytest.mark.flaky(reruns=RERUNS, reruns_delay=RERUNS_DELAY)
|
|
439
|
+
def test_get_pvgis_tmy_coerce_year():
|
|
440
|
+
"""test utc_offset and coerce_year work as expected"""
|
|
441
|
+
base_case, _, _, _ = get_pvgis_tmy(45, 8) # Turin
|
|
442
|
+
assert str(base_case.index.tz) == 'UTC'
|
|
443
|
+
assert base_case.index.name == 'time(UTC)'
|
|
444
|
+
noon_test_data = [
|
|
445
|
+
base_case[base_case.index.month == m].iloc[12]
|
|
446
|
+
for m in range(1, 13)]
|
|
447
|
+
cet_tz = 1 # Turin time is CET
|
|
448
|
+
cet_name = 'Etc/GMT-1'
|
|
449
|
+
# check indices of rolled data after converting timezone
|
|
450
|
+
pvgis_data, _, _, _ = get_pvgis_tmy(45, 8, roll_utc_offset=cet_tz)
|
|
451
|
+
jan1_midnight = pd.Timestamp('1990-01-01 00:00:00', tz=cet_name)
|
|
452
|
+
dec31_midnight = pd.Timestamp('1990-12-31 23:00:00', tz=cet_name)
|
|
453
|
+
assert pvgis_data.index[0] == jan1_midnight
|
|
454
|
+
assert pvgis_data.index[-1] == dec31_midnight
|
|
455
|
+
assert pvgis_data.index.name == f'time({cet_name})'
|
|
456
|
+
# spot check rolled data matches original
|
|
457
|
+
for m, test_case in enumerate(noon_test_data):
|
|
458
|
+
expected = pvgis_data[pvgis_data.index.month == m+1].iloc[12+cet_tz]
|
|
459
|
+
assert all(test_case == expected)
|
|
460
|
+
# repeat tests with year coerced
|
|
461
|
+
test_yr = 2021
|
|
462
|
+
pvgis_data, _, _, _ = get_pvgis_tmy(
|
|
463
|
+
45, 8, roll_utc_offset=cet_tz, coerce_year=test_yr)
|
|
464
|
+
jan1_midnight = pd.Timestamp(f'{test_yr}-01-01 00:00:00', tz=cet_name)
|
|
465
|
+
dec31_midnight = pd.Timestamp(f'{test_yr}-12-31 23:00:00', tz=cet_name)
|
|
466
|
+
assert pvgis_data.index[0] == jan1_midnight
|
|
467
|
+
assert pvgis_data.index[-1] == dec31_midnight
|
|
468
|
+
assert pvgis_data.index.name == f'time({cet_name})'
|
|
469
|
+
for m, test_case in enumerate(noon_test_data):
|
|
470
|
+
expected = pvgis_data[pvgis_data.index.month == m+1].iloc[12+cet_tz]
|
|
471
|
+
assert all(test_case == expected)
|
|
472
|
+
# repeat tests with year coerced but utc offset none or zero
|
|
473
|
+
pvgis_data, _, _, _ = get_pvgis_tmy(45, 8, coerce_year=test_yr)
|
|
474
|
+
jan1_midnight = pd.Timestamp(f'{test_yr}-01-01 00:00:00', tz='UTC')
|
|
475
|
+
dec31_midnight = pd.Timestamp(f'{test_yr}-12-31 23:00:00', tz='UTC')
|
|
476
|
+
assert pvgis_data.index[0] == jan1_midnight
|
|
477
|
+
assert pvgis_data.index[-1] == dec31_midnight
|
|
478
|
+
assert pvgis_data.index.name == 'time(UTC)'
|
|
479
|
+
for m, test_case in enumerate(noon_test_data):
|
|
480
|
+
expected = pvgis_data[pvgis_data.index.month == m+1].iloc[12]
|
|
481
|
+
assert all(test_case == expected)
|
|
482
|
+
|
|
483
|
+
|
|
438
484
|
@pytest.mark.remote_data
|
|
439
485
|
@pytest.mark.flaky(reruns=RERUNS, reruns_delay=RERUNS_DELAY)
|
|
440
486
|
def test_get_pvgis_tmy_csv(expected, month_year_expected, inputs_expected,
|
|
@@ -518,14 +564,14 @@ def test_read_pvgis_horizon_invalid_coords():
|
|
|
518
564
|
|
|
519
565
|
|
|
520
566
|
def test_read_pvgis_tmy_map_variables(pvgis_tmy_mapped_columns):
|
|
521
|
-
fn = DATA_DIR / 'tmy_45.000_8.
|
|
567
|
+
fn = DATA_DIR / 'tmy_45.000_8.000_2005_2020.json'
|
|
522
568
|
actual, _, _, _ = read_pvgis_tmy(fn, map_variables=True)
|
|
523
569
|
assert all(c in pvgis_tmy_mapped_columns for c in actual.columns)
|
|
524
570
|
|
|
525
571
|
|
|
526
572
|
def test_read_pvgis_tmy_json(expected, month_year_expected, inputs_expected,
|
|
527
573
|
meta_expected):
|
|
528
|
-
fn = DATA_DIR / 'tmy_45.000_8.
|
|
574
|
+
fn = DATA_DIR / 'tmy_45.000_8.000_2005_2020.json'
|
|
529
575
|
# infer outputformat from file extensions
|
|
530
576
|
pvgis_data = read_pvgis_tmy(fn, map_variables=False)
|
|
531
577
|
_compare_pvgis_tmy_json(expected, month_year_expected, inputs_expected,
|
|
@@ -542,7 +588,7 @@ def test_read_pvgis_tmy_json(expected, month_year_expected, inputs_expected,
|
|
|
542
588
|
|
|
543
589
|
|
|
544
590
|
def test_read_pvgis_tmy_epw(expected, epw_meta):
|
|
545
|
-
fn = DATA_DIR / 'tmy_45.000_8.
|
|
591
|
+
fn = DATA_DIR / 'tmy_45.000_8.000_2005_2020.epw'
|
|
546
592
|
# infer outputformat from file extensions
|
|
547
593
|
pvgis_data = read_pvgis_tmy(fn, map_variables=False)
|
|
548
594
|
_compare_pvgis_tmy_epw(expected, epw_meta, pvgis_data)
|
|
@@ -557,7 +603,7 @@ def test_read_pvgis_tmy_epw(expected, epw_meta):
|
|
|
557
603
|
|
|
558
604
|
def test_read_pvgis_tmy_csv(expected, month_year_expected, inputs_expected,
|
|
559
605
|
meta_expected, csv_meta):
|
|
560
|
-
fn = DATA_DIR / 'tmy_45.000_8.
|
|
606
|
+
fn = DATA_DIR / 'tmy_45.000_8.000_2005_2020.csv'
|
|
561
607
|
# infer outputformat from file extensions
|
|
562
608
|
pvgis_data = read_pvgis_tmy(fn, map_variables=False)
|
|
563
609
|
_compare_pvgis_tmy_csv(expected, month_year_expected, inputs_expected,
|
|
@@ -574,7 +620,7 @@ def test_read_pvgis_tmy_csv(expected, month_year_expected, inputs_expected,
|
|
|
574
620
|
|
|
575
621
|
|
|
576
622
|
def test_read_pvgis_tmy_basic(expected, meta_expected):
|
|
577
|
-
fn = DATA_DIR / 'tmy_45.000_8.
|
|
623
|
+
fn = DATA_DIR / 'tmy_45.000_8.000_2005_2020.txt'
|
|
578
624
|
# XXX: can't infer outputformat from file extensions for basic
|
|
579
625
|
with pytest.raises(ValueError, match="pvgis format 'txt' was unknown"):
|
|
580
626
|
read_pvgis_tmy(fn, map_variables=False)
|
|
File without changes
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pytest
|
|
3
|
+
import pandas as pd
|
|
4
|
+
|
|
5
|
+
from ..conftest import DATA_DIR
|
|
6
|
+
|
|
7
|
+
SPECTRL2_TEST_DATA = DATA_DIR / 'spectrl2_example_spectra.csv'
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pytest.fixture
|
|
11
|
+
def spectrl2_data():
|
|
12
|
+
# reference spectra generated with solar_utils==0.3
|
|
13
|
+
"""
|
|
14
|
+
expected = solar_utils.spectrl2(
|
|
15
|
+
units=1,
|
|
16
|
+
location=[40, -80, -5],
|
|
17
|
+
datetime=[2020, 3, 15, 10, 45, 59],
|
|
18
|
+
weather=[1013, 15],
|
|
19
|
+
orientation=[0, 180],
|
|
20
|
+
atmospheric_conditions=[1.14, 0.65, 0.344, 0.1, 1.42],
|
|
21
|
+
albedo=[0.3, 0.7, 0.8, 1.3, 2.5, 4.0] + [0.2]*6,
|
|
22
|
+
)
|
|
23
|
+
"""
|
|
24
|
+
kwargs = {
|
|
25
|
+
'surface_tilt': 0,
|
|
26
|
+
'relative_airmass': 1.4899535986910446,
|
|
27
|
+
'apparent_zenith': 47.912086486816406,
|
|
28
|
+
'aoi': 47.91208648681641,
|
|
29
|
+
'ground_albedo': 0.2,
|
|
30
|
+
'surface_pressure': 101300,
|
|
31
|
+
'ozone': 0.344,
|
|
32
|
+
'precipitable_water': 1.42,
|
|
33
|
+
'aerosol_turbidity_500nm': 0.1,
|
|
34
|
+
'dayofyear': 75
|
|
35
|
+
}
|
|
36
|
+
df = pd.read_csv(SPECTRL2_TEST_DATA, index_col=0)
|
|
37
|
+
# convert um to nm
|
|
38
|
+
df['wavelength'] = np.round(df['wavelength'] * 1000, 1)
|
|
39
|
+
df[['specdif', 'specdir', 'specetr', 'specglo']] /= 1000
|
|
40
|
+
return kwargs, df
|