pvlib 0.11.0a1__py3-none-any.whl → 0.11.2__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.
Files changed (62) hide show
  1. pvlib/_deprecation.py +73 -0
  2. pvlib/atmosphere.py +236 -1
  3. pvlib/bifacial/__init__.py +4 -4
  4. pvlib/bifacial/loss_models.py +163 -0
  5. pvlib/clearsky.py +53 -51
  6. pvlib/data/pvgis_tmy_meta.json +32 -93
  7. pvlib/data/pvgis_tmy_test.csv +8761 -0
  8. pvlib/data/tmy_45.000_8.000_2005_2023.csv +8789 -0
  9. pvlib/data/tmy_45.000_8.000_2005_2023.epw +8768 -0
  10. pvlib/data/tmy_45.000_8.000_2005_2023.json +1 -0
  11. pvlib/data/tmy_45.000_8.000_2005_2023.txt +8761 -0
  12. pvlib/data/tmy_45.000_8.000_userhorizon.json +1 -1
  13. pvlib/iam.py +4 -4
  14. pvlib/iotools/midc.py +1 -1
  15. pvlib/iotools/pvgis.py +39 -13
  16. pvlib/irradiance.py +237 -173
  17. pvlib/ivtools/sdm.py +75 -52
  18. pvlib/location.py +5 -5
  19. pvlib/modelchain.py +1 -1
  20. pvlib/pvsystem.py +134 -86
  21. pvlib/shading.py +8 -8
  22. pvlib/singlediode.py +1 -1
  23. pvlib/solarposition.py +101 -80
  24. pvlib/spa.py +28 -24
  25. pvlib/spectrum/__init__.py +9 -4
  26. pvlib/spectrum/irradiance.py +273 -0
  27. pvlib/spectrum/mismatch.py +118 -508
  28. pvlib/spectrum/response.py +280 -0
  29. pvlib/spectrum/spectrl2.py +18 -17
  30. pvlib/temperature.py +49 -3
  31. pvlib/tests/bifacial/test_losses_models.py +54 -0
  32. pvlib/tests/iotools/test_pvgis.py +58 -12
  33. pvlib/tests/ivtools/test_sdm.py +23 -1
  34. pvlib/tests/spectrum/__init__.py +0 -0
  35. pvlib/tests/spectrum/conftest.py +40 -0
  36. pvlib/tests/spectrum/test_irradiance.py +138 -0
  37. pvlib/tests/{test_spectrum.py → spectrum/test_mismatch.py} +32 -306
  38. pvlib/tests/spectrum/test_response.py +124 -0
  39. pvlib/tests/spectrum/test_spectrl2.py +72 -0
  40. pvlib/tests/test__deprecation.py +97 -0
  41. pvlib/tests/test_atmosphere.py +218 -0
  42. pvlib/tests/test_clearsky.py +44 -26
  43. pvlib/tests/test_conftest.py +0 -44
  44. pvlib/tests/test_irradiance.py +62 -16
  45. pvlib/tests/test_pvsystem.py +17 -1
  46. pvlib/tests/test_solarposition.py +117 -36
  47. pvlib/tests/test_spa.py +30 -1
  48. pvlib/tools.py +26 -2
  49. pvlib/tracking.py +53 -47
  50. {pvlib-0.11.0a1.dist-info → pvlib-0.11.2.dist-info}/METADATA +34 -31
  51. {pvlib-0.11.0a1.dist-info → pvlib-0.11.2.dist-info}/RECORD +55 -47
  52. {pvlib-0.11.0a1.dist-info → pvlib-0.11.2.dist-info}/WHEEL +1 -1
  53. pvlib/data/aod550_tcwv_20121101_test.nc +0 -0
  54. pvlib/data/pvgis_tmy_test.dat +0 -8761
  55. pvlib/data/tmy_45.000_8.000_2005_2016.csv +0 -8789
  56. pvlib/data/tmy_45.000_8.000_2005_2016.epw +0 -8768
  57. pvlib/data/tmy_45.000_8.000_2005_2016.json +0 -1
  58. pvlib/data/tmy_45.000_8.000_2005_2016.txt +0 -8761
  59. pvlib/data/variables_style_rules.csv +0 -55
  60. {pvlib-0.11.0a1.dist-info → pvlib-0.11.2.dist-info}/AUTHORS.md +0 -0
  61. {pvlib-0.11.0a1.dist-info → pvlib-0.11.2.dist-info}/LICENSE +0 -0
  62. {pvlib-0.11.0a1.dist-info → pvlib-0.11.2.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
@@ -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]. Must be provided if ``apparent_zenith`` is
216
- not a pandas Series.
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,16 @@ 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
- spectra : dict
231
- A dict of arrays. With the exception of `wavelength`, which has length
230
+ spectra_components : dict
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 W/m^2/nm except for `wavelength`, which is in nanometers.
234
+ with units Wm⁻²nm⁻¹, except for `wavelength`, which is in nanometers.
235
+ See :term:`spectra_components`.
235
236
 
236
237
  * wavelength
237
238
  * dni_extra
@@ -267,7 +268,7 @@ def spectrl2(apparent_zenith, aoi, surface_tilt, ground_albedo,
267
268
 
268
269
  References
269
270
  ----------
270
- .. [1] Bird, R, and Riordan, C., 1984, "Simple solar spectral model for
271
+ .. [1] Bird, R., and Riordan, C., 1984, "Simple solar spectral model for
271
272
  direct and diffuse irradiance on horizontal and tilted planes at the
272
273
  earth's surface for cloudless atmospheres", NREL Technical Report
273
274
  TR-215-2436 :doi:`10.2172/5986936`.
@@ -288,7 +289,7 @@ def spectrl2(apparent_zenith, aoi, surface_tilt, ground_albedo,
288
289
  aerosol_turbidity_500nm, scattering_albedo_400nm, alpha,
289
290
  wavelength_variation_factor, aerosol_asymmetry_factor]))
290
291
 
291
- dayofyear = original_index.dayofyear.values
292
+ dayofyear = pvlib.tools._pandas_to_doy(original_index).values
292
293
 
293
294
  if not is_pandas and dayofyear is None:
294
295
  raise ValueError('dayofyear must be specified if not using pandas '
@@ -363,7 +364,7 @@ def spectrl2(apparent_zenith, aoi, surface_tilt, ground_albedo,
363
364
 
364
365
  # calculate spectral irradiance on a tilted surface, Eq 3-18
365
366
  # Note: clipping cosd(aoi) to >=0 is not in the reference, but is necessary
366
- # to prevent nonsense values when the sun is behind the plane of array.
367
+ # to prevent negative values when the sun is behind the plane of array.
367
368
  # The same constraint is applied in irradiance.haydavies when not
368
369
  # supplying `projection_ratio`.
369
370
  aoi_projection_nn = np.maximum(cosd(aoi), 0) # GH 1348
pvlib/temperature.py CHANGED
@@ -118,13 +118,25 @@ def sapm_cell(poa_global, temp_air, wind_speed, a, b, deltaT,
118
118
  +===============+================+=======+=========+=====================+
119
119
  | glass/glass | open rack | -3.47 | -0.0594 | 3 |
120
120
  +---------------+----------------+-------+---------+---------------------+
121
- | glass/glass | close roof | -2.98 | -0.0471 | 1 |
121
+ | glass/glass | close mount | -2.98 | -0.0471 | 1 |
122
122
  +---------------+----------------+-------+---------+---------------------+
123
123
  | glass/polymer | open rack | -3.56 | -0.075 | 3 |
124
124
  +---------------+----------------+-------+---------+---------------------+
125
125
  | glass/polymer | insulated back | -2.81 | -0.0455 | 0 |
126
126
  +---------------+----------------+-------+---------+---------------------+
127
127
 
128
+ Mounting cases can be described in terms of air flow across and around the
129
+ rear-facing surface of the module:
130
+
131
+ * "open rack" refers to mounting that allows relatively free air flow.
132
+ This case is typical of ground-mounted systems on fixed racking or
133
+ single axis trackers.
134
+ * "close mount" refers to limited or restricted air flow. This case is
135
+ typical of roof-mounted systems with some gap behind the module.
136
+ * "insulated back" refers to systems with no air flow contacting the rear
137
+ surface of the module. This case is typical of building-integrated PV
138
+ systems, or systems laid flat on a ground surface.
139
+
128
140
  References
129
141
  ----------
130
142
  .. [1] King, D. et al, 2004, "Sandia Photovoltaic Array Performance
@@ -199,13 +211,25 @@ def sapm_module(poa_global, temp_air, wind_speed, a, b):
199
211
  +===============+================+=======+=========+=====================+
200
212
  | glass/glass | open rack | -3.47 | -0.0594 | 3 |
201
213
  +---------------+----------------+-------+---------+---------------------+
202
- | glass/glass | close roof | -2.98 | -0.0471 | 1 |
214
+ | glass/glass | close mount | -2.98 | -0.0471 | 1 |
203
215
  +---------------+----------------+-------+---------+---------------------+
204
216
  | glass/polymer | open rack | -3.56 | -0.075 | 3 |
205
217
  +---------------+----------------+-------+---------+---------------------+
206
218
  | glass/polymer | insulated back | -2.81 | -0.0455 | 0 |
207
219
  +---------------+----------------+-------+---------+---------------------+
208
220
 
221
+ Mounting cases can be described in terms of air flow across and around the
222
+ rear-facing surface of the module:
223
+
224
+ * "open rack" refers to mounting that allows relatively free air flow.
225
+ This case is typical of ground-mounted systems on fixed racking or
226
+ single axis trackers.
227
+ * "close mount" refers to limited or restricted air flow. This case is
228
+ typical of roof-mounted systems with some gap behind the module.
229
+ * "insulated back" refers to systems with no air flow contacting the rear
230
+ surface of the module. This case is typical of building-integrated PV
231
+ systems, or systems laid flat on a ground surface.
232
+
209
233
  References
210
234
  ----------
211
235
  .. [1] King, D. et al, 2004, "Sandia Photovoltaic Array Performance
@@ -269,13 +293,25 @@ def sapm_cell_from_module(module_temperature, poa_global, deltaT,
269
293
  +===============+================+=======+=========+=====================+
270
294
  | glass/glass | open rack | -3.47 | -0.0594 | 3 |
271
295
  +---------------+----------------+-------+---------+---------------------+
272
- | glass/glass | close roof | -2.98 | -0.0471 | 1 |
296
+ | glass/glass | close mount | -2.98 | -0.0471 | 1 |
273
297
  +---------------+----------------+-------+---------+---------------------+
274
298
  | glass/polymer | open rack | -3.56 | -0.075 | 3 |
275
299
  +---------------+----------------+-------+---------+---------------------+
276
300
  | glass/polymer | insulated back | -2.81 | -0.0455 | 0 |
277
301
  +---------------+----------------+-------+---------+---------------------+
278
302
 
303
+ Mounting cases can be described in terms of air flow across and around the
304
+ rear-facing surface of the module:
305
+
306
+ * "open rack" refers to mounting that allows relatively free air flow.
307
+ This case is typical of ground-mounted systems on fixed racking or
308
+ single axis trackers.
309
+ * "close mount" refers to limited or restricted air flow. This case is
310
+ typical of roof-mounted systems with some gap behind the module.
311
+ * "insulated back" refers to systems with no air flow contacting the rear
312
+ surface of the module. This case is typical of building-integrated PV
313
+ systems, or systems laid flat on a ground surface.
314
+
279
315
  References
280
316
  ----------
281
317
  .. [1] King, D. et al, 2004, "Sandia Photovoltaic Array Performance
@@ -360,6 +396,16 @@ def pvsyst_cell(poa_global, temp_air, wind_speed=1.0, u_c=29.0, u_v=0.0,
360
396
  | insulated | 15.0 | 0.0 |
361
397
  +--------------+---------------+---------------+
362
398
 
399
+ Mounting cases can be described in terms of air flow across and around the
400
+ rear-facing surface of the module:
401
+
402
+ * "freestanding" refers to mounting that allows relatively free air
403
+ circulation around the modules. This case is typical of ground-mounted
404
+ systems on tilted, fixed racking or single axis trackers.
405
+ * "insulated" refers to mounting with air flow across only the front
406
+ surface. This case is typical of roof-mounted systems with no gap
407
+ behind the module.
408
+
363
409
  References
364
410
  ----------
365
411
  .. [1] "PVsyst 7 Help", [Online]. Available:
@@ -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
- fail_on_pvlib_version, assert_series_equal)
15
- from pvlib._deprecation import pvlibDeprecationWarning
14
+ assert_series_equal)
16
15
 
17
16
 
18
17
  # PVGIS Hourly tests
@@ -305,7 +304,7 @@ def test_read_pvgis_hourly_empty_file():
305
304
  # PVGIS TMY tests
306
305
  @pytest.fixture
307
306
  def expected():
308
- return pd.read_csv(DATA_DIR / 'pvgis_tmy_test.dat', index_col='time(UTC)')
307
+ return pd.read_csv(DATA_DIR / 'pvgis_tmy_test.csv', index_col='time(UTC)')
309
308
 
310
309
 
311
310
  @pytest.fixture
@@ -316,7 +315,7 @@ def userhorizon_expected():
316
315
  @pytest.fixture
317
316
  def month_year_expected():
318
317
  return [
319
- 2009, 2012, 2014, 2010, 2011, 2013, 2011, 2011, 2013, 2013, 2013, 2011]
318
+ 2018, 2007, 2009, 2013, 2008, 2006, 2011, 2010, 2020, 2006, 2007, 2016]
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-SARAH',
328
- 'meteo_db': 'ERA-Interim',
326
+ 'radiation_db': 'PVGIS-SARAH3',
327
+ 'meteo_db': 'ERA5',
329
328
  'year_min': 2005,
330
- 'year_max': 2016,
329
+ 'year_max': 2023,
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.000_2005_2016.json'
567
+ fn = DATA_DIR / 'tmy_45.000_8.000_2005_2023.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.000_2005_2016.json'
574
+ fn = DATA_DIR / 'tmy_45.000_8.000_2005_2023.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.000_2005_2016.epw'
591
+ fn = DATA_DIR / 'tmy_45.000_8.000_2005_2023.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.000_2005_2016.csv'
606
+ fn = DATA_DIR / 'tmy_45.000_8.000_2005_2023.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.000_2005_2016.txt'
623
+ fn = DATA_DIR / 'tmy_45.000_8.000_2005_2023.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)