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,124 @@
1
+ import pytest
2
+ import pandas as pd
3
+ from numpy.testing import assert_allclose, assert_approx_equal, assert_equal
4
+ import numpy as np
5
+ from pvlib import spectrum
6
+
7
+
8
+ def test_get_example_spectral_response():
9
+ # test that the sample sr is read and interpolated correctly
10
+ sr = spectrum.get_example_spectral_response()
11
+ assert_equal(len(sr), 185)
12
+ assert_equal(np.sum(sr.index), 136900)
13
+ assert_approx_equal(np.sum(sr), 107.6116)
14
+
15
+ wavelength = [270, 850, 950, 1200, 4001]
16
+ expected = [0.0, 0.92778, 1.0, 0.0, 0.0]
17
+
18
+ sr = spectrum.get_example_spectral_response(wavelength)
19
+ assert_equal(len(sr), len(wavelength))
20
+ assert_allclose(sr, expected, rtol=1e-5)
21
+
22
+
23
+ @pytest.fixture
24
+ def sr_and_eqe_fixture():
25
+ # Just some arbitrary data for testing the conversion functions
26
+ df = pd.DataFrame(
27
+ columns=("wavelength", "quantum_efficiency", "spectral_response"),
28
+ data=[
29
+ # nm, [0,1], A/W
30
+ [300, 0.85, 0.205671370402405],
31
+ [350, 0.86, 0.242772872514211],
32
+ [400, 0.87, 0.280680929019753],
33
+ [450, 0.88, 0.319395539919029],
34
+ [500, 0.89, 0.358916705212040],
35
+ [550, 0.90, 0.399244424898786],
36
+ [600, 0.91, 0.440378698979267],
37
+ [650, 0.92, 0.482319527453483],
38
+ [700, 0.93, 0.525066910321434],
39
+ [750, 0.94, 0.568620847583119],
40
+ [800, 0.95, 0.612981339238540],
41
+ [850, 0.90, 0.617014111207215],
42
+ [900, 0.80, 0.580719163489143],
43
+ [950, 0.70, 0.536358671833723],
44
+ [1000, 0.6, 0.483932636240953],
45
+ [1050, 0.4, 0.338752845368667],
46
+ ],
47
+ )
48
+ df.set_index("wavelength", inplace=True)
49
+ return df
50
+
51
+
52
+ def test_sr_to_qe(sr_and_eqe_fixture):
53
+ # vector type
54
+ qe = spectrum.sr_to_qe(
55
+ sr_and_eqe_fixture["spectral_response"].values,
56
+ sr_and_eqe_fixture.index.values, # wavelength, nm
57
+ )
58
+ assert_allclose(qe, sr_and_eqe_fixture["quantum_efficiency"])
59
+ # pandas series type
60
+ # note: output Series' name should match the input
61
+ qe = spectrum.sr_to_qe(
62
+ sr_and_eqe_fixture["spectral_response"]
63
+ )
64
+ pd.testing.assert_series_equal(
65
+ qe, sr_and_eqe_fixture["quantum_efficiency"],
66
+ check_names=False
67
+ )
68
+ assert qe.name == "spectral_response"
69
+ # series normalization
70
+ qe = spectrum.sr_to_qe(
71
+ sr_and_eqe_fixture["spectral_response"] * 10, normalize=True
72
+ )
73
+ pd.testing.assert_series_equal(
74
+ qe,
75
+ sr_and_eqe_fixture["quantum_efficiency"]
76
+ / max(sr_and_eqe_fixture["quantum_efficiency"]),
77
+ check_names=False,
78
+ )
79
+ # error on lack of wavelength parameter if no pandas object is provided
80
+ with pytest.raises(TypeError, match="must have an '.index' attribute"):
81
+ _ = spectrum.sr_to_qe(sr_and_eqe_fixture["spectral_response"].values)
82
+
83
+
84
+ def test_qe_to_sr(sr_and_eqe_fixture):
85
+ # vector type
86
+ sr = spectrum.qe_to_sr(
87
+ sr_and_eqe_fixture["quantum_efficiency"].values,
88
+ sr_and_eqe_fixture.index.values, # wavelength, nm
89
+ )
90
+ assert_allclose(sr, sr_and_eqe_fixture["spectral_response"])
91
+ # pandas series type
92
+ # note: output Series' name should match the input
93
+ sr = spectrum.qe_to_sr(
94
+ sr_and_eqe_fixture["quantum_efficiency"]
95
+ )
96
+ pd.testing.assert_series_equal(
97
+ sr, sr_and_eqe_fixture["spectral_response"],
98
+ check_names=False
99
+ )
100
+ assert sr.name == "quantum_efficiency"
101
+ # series normalization
102
+ sr = spectrum.qe_to_sr(
103
+ sr_and_eqe_fixture["quantum_efficiency"] * 10, normalize=True
104
+ )
105
+ pd.testing.assert_series_equal(
106
+ sr,
107
+ sr_and_eqe_fixture["spectral_response"]
108
+ / max(sr_and_eqe_fixture["spectral_response"]),
109
+ check_names=False,
110
+ )
111
+ # error on lack of wavelength parameter if no pandas object is provided
112
+ with pytest.raises(TypeError, match="must have an '.index' attribute"):
113
+ _ = spectrum.qe_to_sr(
114
+ sr_and_eqe_fixture["quantum_efficiency"].values
115
+ )
116
+
117
+
118
+ def test_qe_and_sr_reciprocal_conversion(sr_and_eqe_fixture):
119
+ # test that the conversion functions are reciprocal
120
+ qe = spectrum.sr_to_qe(sr_and_eqe_fixture["spectral_response"])
121
+ sr = spectrum.qe_to_sr(qe)
122
+ assert_allclose(sr, sr_and_eqe_fixture["spectral_response"])
123
+ qe = spectrum.sr_to_qe(sr)
124
+ assert_allclose(qe, sr_and_eqe_fixture["quantum_efficiency"])
@@ -0,0 +1,72 @@
1
+ import pytest
2
+ import pandas as pd
3
+ import numpy as np
4
+ from pvlib import spectrum
5
+ from numpy.testing import assert_allclose
6
+
7
+
8
+ def test_spectrl2(spectrl2_data):
9
+ # compare against output from solar_utils wrapper around NREL spectrl2_2.c
10
+ kwargs, expected = spectrl2_data
11
+ actual = spectrum.spectrl2(**kwargs)
12
+ assert_allclose(expected['wavelength'].values, actual['wavelength'])
13
+ assert_allclose(expected['specdif'].values, actual['dhi'].ravel(),
14
+ atol=7e-5)
15
+ assert_allclose(expected['specdir'].values, actual['dni'].ravel(),
16
+ atol=1.5e-4)
17
+ assert_allclose(expected['specetr'], actual['dni_extra'].ravel(),
18
+ atol=2e-4)
19
+ assert_allclose(expected['specglo'], actual['poa_global'].ravel(),
20
+ atol=1e-4)
21
+
22
+
23
+ def test_spectrl2_array(spectrl2_data):
24
+ # test that supplying arrays instead of scalars works
25
+ kwargs, expected = spectrl2_data
26
+ kwargs = {k: np.array([v, v, v]) for k, v in kwargs.items()}
27
+ actual = spectrum.spectrl2(**kwargs)
28
+
29
+ assert actual['wavelength'].shape == (122,)
30
+
31
+ keys = ['dni_extra', 'dhi', 'dni', 'poa_sky_diffuse', 'poa_ground_diffuse',
32
+ 'poa_direct', 'poa_global']
33
+ for key in keys:
34
+ assert actual[key].shape == (122, 3)
35
+
36
+
37
+ def test_spectrl2_series(spectrl2_data):
38
+ # test that supplying Series instead of scalars works
39
+ kwargs, expected = spectrl2_data
40
+ kwargs.pop('dayofyear')
41
+ index = pd.to_datetime(['2020-03-15 10:45:59']*3)
42
+ kwargs = {k: pd.Series([v, v, v], index=index) for k, v in kwargs.items()}
43
+ actual = spectrum.spectrl2(**kwargs)
44
+
45
+ assert actual['wavelength'].shape == (122,)
46
+
47
+ keys = ['dni_extra', 'dhi', 'dni', 'poa_sky_diffuse', 'poa_ground_diffuse',
48
+ 'poa_direct', 'poa_global']
49
+ for key in keys:
50
+ assert actual[key].shape == (122, 3)
51
+
52
+
53
+ def test_dayofyear_missing(spectrl2_data):
54
+ # test that not specifying dayofyear with non-pandas inputs raises error
55
+ kwargs, expected = spectrl2_data
56
+ kwargs.pop('dayofyear')
57
+ with pytest.raises(ValueError, match='dayofyear must be specified'):
58
+ _ = spectrum.spectrl2(**kwargs)
59
+
60
+
61
+ def test_aoi_gt_90(spectrl2_data):
62
+ # test that returned irradiance values are non-negative when aoi > 90
63
+ # see GH #1348
64
+ kwargs, _ = spectrl2_data
65
+ kwargs['apparent_zenith'] = 70
66
+ kwargs['aoi'] = 130
67
+ kwargs['surface_tilt'] = 60
68
+
69
+ spectra = spectrum.spectrl2(**kwargs)
70
+ for key in ['poa_direct', 'poa_global']:
71
+ message = f'{key} contains negative values for aoi>90'
72
+ assert np.all(spectra[key] >= 0), message
@@ -0,0 +1,97 @@
1
+ """
2
+ Test the _deprecation module.
3
+ """
4
+
5
+ import pytest
6
+
7
+ from pvlib import _deprecation
8
+ from .conftest import fail_on_pvlib_version
9
+
10
+ import warnings
11
+
12
+
13
+ @pytest.mark.xfail(strict=True,
14
+ reason='fail_on_pvlib_version should cause test to fail')
15
+ @fail_on_pvlib_version('0.0')
16
+ def test_fail_on_pvlib_version():
17
+ pass # pragma: no cover
18
+
19
+
20
+ @fail_on_pvlib_version('100000.0')
21
+ def test_fail_on_pvlib_version_pass():
22
+ pass
23
+
24
+
25
+ @pytest.mark.xfail(strict=True, reason='ensure that the test is called')
26
+ @fail_on_pvlib_version('100000.0')
27
+ def test_fail_on_pvlib_version_fail_in_test():
28
+ raise Exception
29
+
30
+
31
+ # set up to test using fixtures with function decorated with
32
+ # conftest.fail_on_pvlib_version
33
+ @pytest.fixture
34
+ def some_data():
35
+ return "some data"
36
+
37
+
38
+ def alt_func(*args):
39
+ return args
40
+
41
+
42
+ @pytest.fixture
43
+ def deprec_func():
44
+ return _deprecation.deprecated(
45
+ "350.8", alternative="alt_func", name="deprec_func", removal="350.9"
46
+ )(alt_func)
47
+
48
+
49
+ @fail_on_pvlib_version('350.9')
50
+ def test_use_fixture_with_decorator(some_data, deprec_func):
51
+ # test that the correct data is returned by the some_data fixture
52
+ assert some_data == "some data"
53
+ with pytest.warns(_deprecation.pvlibDeprecationWarning):
54
+ # test for custom deprecation warning provided by pvlib
55
+ deprec_func(some_data)
56
+
57
+
58
+ @pytest.fixture
59
+ def renamed_kwarg_func():
60
+ """Returns a function decorated by renamed_kwarg_warning.
61
+ This function is called 'func' and has a docstring equal to 'docstring'.
62
+ """
63
+
64
+ @_deprecation.renamed_kwarg_warning(
65
+ "0.1.0", "old_kwarg", "new_kwarg", "0.2.0"
66
+ )
67
+ def func(new_kwarg):
68
+ """docstring"""
69
+ return new_kwarg
70
+
71
+ return func
72
+
73
+
74
+ def test_renamed_kwarg_warning(renamed_kwarg_func):
75
+ # assert decorated function name and docstring are unchanged
76
+ assert renamed_kwarg_func.__name__ == "func"
77
+ assert renamed_kwarg_func.__doc__ == "docstring"
78
+
79
+ # assert no warning is raised when using the new kwarg
80
+ with warnings.catch_warnings():
81
+ warnings.simplefilter("error")
82
+ assert renamed_kwarg_func(new_kwarg=1) == 1 # as keyword argument
83
+ assert renamed_kwarg_func(1) == 1 # as positional argument
84
+
85
+ # assert a warning is raised when using the old kwarg
86
+ with pytest.warns(Warning, match="Parameter 'old_kwarg' has been renamed"):
87
+ assert renamed_kwarg_func(old_kwarg=1) == 1
88
+
89
+ # assert an error is raised when using both the old and new kwarg
90
+ with pytest.raises(ValueError, match="they refer to the same parameter."):
91
+ renamed_kwarg_func(old_kwarg=1, new_kwarg=2)
92
+
93
+ # assert when not providing any of them
94
+ with pytest.raises(
95
+ TypeError, match="missing 1 required positional argument"
96
+ ):
97
+ renamed_kwarg_func()
@@ -88,6 +88,153 @@ def test_gueymard94_pw():
88
88
  assert_allclose(pws, expected, atol=0.01)
89
89
 
90
90
 
91
+ def test_tdew_to_rh_to_tdew():
92
+
93
+ # dewpoint temp calculated with wmo and aekr coefficients
94
+ dewpoint_original = pd.Series([
95
+ 15.0, 20.0, 25.0, 12.0, 8.0
96
+ ])
97
+
98
+ temperature_ambient = pd.Series([20.0, 25.0, 30.0, 15.0, 10.0])
99
+
100
+ # Calculate relative humidity using pandas series as input
101
+ relative_humidity = atmosphere.rh_from_tdew(
102
+ temp_air=temperature_ambient,
103
+ temp_dew=dewpoint_original
104
+ )
105
+
106
+ dewpoint_calculated = atmosphere.tdew_from_rh(
107
+ temp_air=temperature_ambient,
108
+ relative_humidity=relative_humidity
109
+ )
110
+
111
+ # test
112
+ pd.testing.assert_series_equal(
113
+ dewpoint_original,
114
+ dewpoint_calculated,
115
+ check_names=False
116
+ )
117
+
118
+
119
+ def test_rh_from_tdew():
120
+
121
+ dewpoint = pd.Series([
122
+ 15.0, 20.0, 25.0, 12.0, 8.0
123
+ ])
124
+
125
+ # relative humidity calculated with wmo and aekr coefficients
126
+ relative_humidity_wmo = pd.Series([
127
+ 72.95185312581116, 73.81500029087906, 74.6401272083123,
128
+ 82.27063889868842, 87.39018119185337
129
+ ])
130
+ relative_humidity_aekr = pd.Series([
131
+ 72.93876680928582, 73.8025121880607, 74.62820502423823,
132
+ 82.26135295757305, 87.38323744820416
133
+ ])
134
+
135
+ temperature_ambient = pd.Series([20.0, 25.0, 30.0, 15.0, 10.0])
136
+
137
+ # Calculate relative humidity using pandas series as input
138
+ rh_series = atmosphere.rh_from_tdew(
139
+ temp_air=temperature_ambient,
140
+ temp_dew=dewpoint
141
+ )
142
+
143
+ pd.testing.assert_series_equal(
144
+ rh_series,
145
+ relative_humidity_wmo,
146
+ check_names=False
147
+ )
148
+
149
+ # Calulate relative humidity using pandas series as input
150
+ # with AEKR coefficients
151
+ rh_series_aekr = atmosphere.rh_from_tdew(
152
+ temp_air=temperature_ambient,
153
+ temp_dew=dewpoint,
154
+ coeff=(6.1094, 17.625, 243.04)
155
+ )
156
+
157
+ pd.testing.assert_series_equal(
158
+ rh_series_aekr,
159
+ relative_humidity_aekr,
160
+ check_names=False
161
+ )
162
+
163
+ # Calculate relative humidity using array as input
164
+ rh_array = atmosphere.rh_from_tdew(
165
+ temp_air=temperature_ambient.to_numpy(),
166
+ temp_dew=dewpoint.to_numpy()
167
+ )
168
+
169
+ np.testing.assert_allclose(rh_array, relative_humidity_wmo.to_numpy())
170
+
171
+ # Calculate relative humidity using float as input
172
+ rh_float = atmosphere.rh_from_tdew(
173
+ temp_air=temperature_ambient.iloc[0],
174
+ temp_dew=dewpoint.iloc[0]
175
+ )
176
+
177
+ assert np.isclose(rh_float, relative_humidity_wmo.iloc[0])
178
+
179
+
180
+ # Unit tests
181
+ def test_tdew_from_rh():
182
+
183
+ dewpoint = pd.Series([
184
+ 15.0, 20.0, 25.0, 12.0, 8.0
185
+ ])
186
+
187
+ # relative humidity calculated with wmo and aekr coefficients
188
+ relative_humidity_wmo = pd.Series([
189
+ 72.95185312581116, 73.81500029087906, 74.6401272083123,
190
+ 82.27063889868842, 87.39018119185337
191
+ ])
192
+ relative_humidity_aekr = pd.Series([
193
+ 72.93876680928582, 73.8025121880607, 74.62820502423823,
194
+ 82.26135295757305, 87.38323744820416
195
+ ])
196
+
197
+ temperature_ambient = pd.Series([20.0, 25.0, 30.0, 15.0, 10.0])
198
+
199
+ # test as series
200
+ dewpoint_series = atmosphere.tdew_from_rh(
201
+ temp_air=temperature_ambient,
202
+ relative_humidity=relative_humidity_wmo
203
+ )
204
+
205
+ pd.testing.assert_series_equal(
206
+ dewpoint_series, dewpoint, check_names=False
207
+ )
208
+
209
+ # test as series with AEKR coefficients
210
+ dewpoint_series_aekr = atmosphere.tdew_from_rh(
211
+ temp_air=temperature_ambient,
212
+ relative_humidity=relative_humidity_aekr,
213
+ coeff=(6.1094, 17.625, 243.04)
214
+ )
215
+
216
+ pd.testing.assert_series_equal(
217
+ dewpoint_series_aekr, dewpoint,
218
+ check_names=False
219
+ )
220
+
221
+ # test as numpy array
222
+ dewpoint_array = atmosphere.tdew_from_rh(
223
+ temp_air=temperature_ambient.to_numpy(),
224
+ relative_humidity=relative_humidity_wmo.to_numpy()
225
+ )
226
+
227
+ np.testing.assert_allclose(dewpoint_array, dewpoint.to_numpy())
228
+
229
+ # test as float
230
+ dewpoint_float = atmosphere.tdew_from_rh(
231
+ temp_air=temperature_ambient.iloc[0],
232
+ relative_humidity=relative_humidity_wmo.iloc[0]
233
+ )
234
+
235
+ assert np.isclose(dewpoint_float, dewpoint.iloc[0])
236
+
237
+
91
238
  def test_first_solar_spectral_correction_deprecated():
92
239
  with pytest.warns(pvlibDeprecationWarning,
93
240
  match='Use pvlib.spectrum.spectral_factor_firstsolar'):
@@ -131,3 +278,74 @@ def test_bird_hulstrom80_aod_bb():
131
278
  aod380, aod500 = 0.22072480948195175, 0.1614279181106312
132
279
  bird_hulstrom = atmosphere.bird_hulstrom80_aod_bb(aod380, aod500)
133
280
  assert np.isclose(0.11738229553812768, bird_hulstrom)
281
+
282
+
283
+ @pytest.fixture
284
+ def windspeeds_data_powerlaw():
285
+ data = pd.DataFrame(
286
+ index=pd.date_range(start="2015-01-01 00:00", end="2015-01-01 05:00",
287
+ freq="1h"),
288
+ columns=["wind_ref", "height_ref", "height_desired", "wind_calc"],
289
+ data=[
290
+ (10, -2, 5, np.nan),
291
+ (-10, 2, 5, np.nan),
292
+ (5, 4, 5, 5.067393209486324),
293
+ (7, 6, 10, 7.2178684911195905),
294
+ (10, 8, 20, 10.565167835216586),
295
+ (12, 10, 30, 12.817653329393977)
296
+ ]
297
+ )
298
+ return data
299
+
300
+
301
+ def test_windspeed_powerlaw_ndarray(windspeeds_data_powerlaw):
302
+ # test wind speed estimation by passing in surface_type
303
+ result_surface = atmosphere.windspeed_powerlaw(
304
+ windspeeds_data_powerlaw["wind_ref"].to_numpy(),
305
+ windspeeds_data_powerlaw["height_ref"],
306
+ windspeeds_data_powerlaw["height_desired"],
307
+ surface_type='unstable_air_above_open_water_surface')
308
+ assert_allclose(windspeeds_data_powerlaw["wind_calc"].to_numpy(),
309
+ result_surface)
310
+ # test wind speed estimation by passing in the exponent corresponding
311
+ # to the surface_type above
312
+ result_exponent = atmosphere.windspeed_powerlaw(
313
+ windspeeds_data_powerlaw["wind_ref"].to_numpy(),
314
+ windspeeds_data_powerlaw["height_ref"],
315
+ windspeeds_data_powerlaw["height_desired"],
316
+ exponent=0.06)
317
+ assert_allclose(windspeeds_data_powerlaw["wind_calc"].to_numpy(),
318
+ result_exponent)
319
+
320
+
321
+ def test_windspeed_powerlaw_series(windspeeds_data_powerlaw):
322
+ result = atmosphere.windspeed_powerlaw(
323
+ windspeeds_data_powerlaw["wind_ref"],
324
+ windspeeds_data_powerlaw["height_ref"],
325
+ windspeeds_data_powerlaw["height_desired"],
326
+ surface_type='unstable_air_above_open_water_surface')
327
+ assert_series_equal(windspeeds_data_powerlaw["wind_calc"],
328
+ result, check_names=False)
329
+
330
+
331
+ def test_windspeed_powerlaw_invalid():
332
+ with pytest.raises(ValueError, match="Either a 'surface_type' or an "
333
+ "'exponent' parameter must be given"):
334
+ # no exponent or surface_type given
335
+ atmosphere.windspeed_powerlaw(wind_speed_reference=10,
336
+ height_reference=5,
337
+ height_desired=10)
338
+ with pytest.raises(ValueError, match="Either a 'surface_type' or an "
339
+ "'exponent' parameter must be given"):
340
+ # no exponent or surface_type given
341
+ atmosphere.windspeed_powerlaw(wind_speed_reference=10,
342
+ height_reference=5,
343
+ height_desired=10,
344
+ exponent=1.2,
345
+ surface_type="surf")
346
+ with pytest.raises(KeyError, match='not_an_exponent'):
347
+ # invalid surface_type
348
+ atmosphere.windspeed_powerlaw(wind_speed_reference=10,
349
+ height_reference=5,
350
+ height_desired=10,
351
+ surface_type='not_an_exponent')
@@ -674,14 +674,25 @@ def test_detect_clearsky_missing_index(detect_clearsky_data):
674
674
 
675
675
  def test_detect_clearsky_not_enough_data(detect_clearsky_data):
676
676
  expected, cs = detect_clearsky_data
677
- with pytest.raises(ValueError, match='have at least'):
678
- clearsky.detect_clearsky(expected['GHI'], cs['ghi'], window_length=60)
677
+ with pytest.raises(ValueError, match='times has only'):
678
+ clearsky.detect_clearsky(expected['GHI'], cs['ghi'], window_length=60)
679
679
 
680
680
 
681
- def test_detect_clearsky_optimizer_failed(detect_clearsky_data):
681
+ def test_detect_clearsky_window_too_short(detect_clearsky_data):
682
682
  expected, cs = detect_clearsky_data
683
- with pytest.raises(RuntimeError, match='Optimizer exited unsuccessfully'):
684
- clearsky.detect_clearsky(expected['GHI'], cs['ghi'], window_length=15)
683
+ with pytest.raises(ValueError, match="Samples per window of "):
684
+ clearsky.detect_clearsky(expected['GHI'], cs['ghi'], window_length=2)
685
+
686
+
687
+ @pytest.mark.parametrize("window_length", [5, 10, 15, 20, 25])
688
+ def test_detect_clearsky_optimizer_not_failed(
689
+ detect_clearsky_data, window_length
690
+ ):
691
+ expected, cs = detect_clearsky_data
692
+ clear_samples = clearsky.detect_clearsky(
693
+ expected["GHI"], cs["ghi"], window_length=window_length
694
+ )
695
+ assert isinstance(clear_samples, pd.Series)
685
696
 
686
697
 
687
698
  @pytest.fixture
@@ -749,6 +760,7 @@ def test_bird():
749
760
  tz = -7 # test timezone
750
761
  gmt_tz = pytz.timezone('Etc/GMT%+d' % -(tz))
751
762
  times = times.tz_localize(gmt_tz) # set timezone
763
+ times_utc = times.tz_convert('UTC')
752
764
  # match test data from BIRD_08_16_2012.xls
753
765
  latitude = 40.
754
766
  longitude = -105.
@@ -759,9 +771,9 @@ def test_bird():
759
771
  aod_380nm = 0.15
760
772
  b_a = 0.85
761
773
  alb = 0.2
762
- eot = solarposition.equation_of_time_spencer71(times.dayofyear)
774
+ eot = solarposition.equation_of_time_spencer71(times_utc.dayofyear)
763
775
  hour_angle = solarposition.hour_angle(times, longitude, eot) - 0.5 * 15.
764
- declination = solarposition.declination_spencer71(times.dayofyear)
776
+ declination = solarposition.declination_spencer71(times_utc.dayofyear)
765
777
  zenith = solarposition.solar_zenith_analytical(
766
778
  np.deg2rad(latitude), np.deg2rad(hour_angle), declination
767
779
  )
@@ -777,32 +789,35 @@ def test_bird():
777
789
  Eb, Ebh, Gh, Dh = (irrads[_] for _ in field_names)
778
790
  data_path = DATA_DIR / 'BIRD_08_16_2012.csv'
779
791
  testdata = pd.read_csv(data_path, usecols=range(1, 26), header=1).dropna()
780
- testdata.index = times[1:48]
781
- assert np.allclose(testdata['DEC'], np.rad2deg(declination[1:48]))
782
- assert np.allclose(testdata['EQT'], eot[1:48], rtol=1e-4)
783
- assert np.allclose(testdata['Hour Angle'], hour_angle[1:48])
784
- assert np.allclose(testdata['Zenith Ang'], zenith[1:48])
792
+ testdata[['DEC', 'EQT']] = testdata[['DEC', 'EQT']].shift(tz)
793
+ testdata = testdata[:tz]
794
+ end = 48 + tz
795
+ testdata.index = times[1:end]
796
+ assert np.allclose(testdata['DEC'], np.rad2deg(declination[1:end]))
797
+ assert np.allclose(testdata['EQT'], eot[1:end], rtol=1e-4)
798
+ assert np.allclose(testdata['Hour Angle'], hour_angle[1:end], rtol=1e-2)
799
+ assert np.allclose(testdata['Zenith Ang'], zenith[1:end], rtol=1e-2)
785
800
  dawn = zenith < 88.
786
801
  dusk = testdata['Zenith Ang'] < 88.
787
802
  am = pd.Series(np.where(dawn, airmass, 0.), index=times).fillna(0.0)
788
803
  assert np.allclose(
789
- testdata['Air Mass'].where(dusk, 0.), am[1:48], rtol=1e-3
804
+ testdata['Air Mass'].where(dusk, 0.), am[1:end], rtol=1e-3
790
805
  )
791
806
  direct_beam = pd.Series(np.where(dawn, Eb, 0.), index=times).fillna(0.)
792
807
  assert np.allclose(
793
- testdata['Direct Beam'].where(dusk, 0.), direct_beam[1:48], rtol=1e-3
808
+ testdata['Direct Beam'].where(dusk, 0.), direct_beam[1:end], rtol=1e-3
794
809
  )
795
810
  direct_horz = pd.Series(np.where(dawn, Ebh, 0.), index=times).fillna(0.)
796
811
  assert np.allclose(
797
- testdata['Direct Hz'].where(dusk, 0.), direct_horz[1:48], rtol=1e-3
812
+ testdata['Direct Hz'].where(dusk, 0.), direct_horz[1:end], rtol=1e-3
798
813
  )
799
814
  global_horz = pd.Series(np.where(dawn, Gh, 0.), index=times).fillna(0.)
800
815
  assert np.allclose(
801
- testdata['Global Hz'].where(dusk, 0.), global_horz[1:48], rtol=1e-3
816
+ testdata['Global Hz'].where(dusk, 0.), global_horz[1:end], rtol=1e-3
802
817
  )
803
818
  diffuse_horz = pd.Series(np.where(dawn, Dh, 0.), index=times).fillna(0.)
804
819
  assert np.allclose(
805
- testdata['Dif Hz'].where(dusk, 0.), diffuse_horz[1:48], rtol=1e-3
820
+ testdata['Dif Hz'].where(dusk, 0.), diffuse_horz[1:end], rtol=1e-3
806
821
  )
807
822
  # repeat test with albedo as a Series
808
823
  alb_series = pd.Series(0.2, index=times)
@@ -813,19 +828,19 @@ def test_bird():
813
828
  Eb, Ebh, Gh, Dh = (irrads[_] for _ in field_names)
814
829
  direct_beam = pd.Series(np.where(dawn, Eb, 0.), index=times).fillna(0.)
815
830
  assert np.allclose(
816
- testdata['Direct Beam'].where(dusk, 0.), direct_beam[1:48], rtol=1e-3
831
+ testdata['Direct Beam'].where(dusk, 0.), direct_beam[1:end], rtol=1e-3
817
832
  )
818
833
  direct_horz = pd.Series(np.where(dawn, Ebh, 0.), index=times).fillna(0.)
819
834
  assert np.allclose(
820
- testdata['Direct Hz'].where(dusk, 0.), direct_horz[1:48], rtol=1e-3
835
+ testdata['Direct Hz'].where(dusk, 0.), direct_horz[1:end], rtol=1e-3
821
836
  )
822
837
  global_horz = pd.Series(np.where(dawn, Gh, 0.), index=times).fillna(0.)
823
838
  assert np.allclose(
824
- testdata['Global Hz'].where(dusk, 0.), global_horz[1:48], rtol=1e-3
839
+ testdata['Global Hz'].where(dusk, 0.), global_horz[1:end], rtol=1e-3
825
840
  )
826
841
  diffuse_horz = pd.Series(np.where(dawn, Dh, 0.), index=times).fillna(0.)
827
842
  assert np.allclose(
828
- testdata['Dif Hz'].where(dusk, 0.), diffuse_horz[1:48], rtol=1e-3
843
+ testdata['Dif Hz'].where(dusk, 0.), diffuse_horz[1:end], rtol=1e-3
829
844
  )
830
845
 
831
846
  # test keyword parameters
@@ -835,22 +850,25 @@ def test_bird():
835
850
  Eb2, Ebh2, Gh2, Dh2 = (irrads2[_] for _ in field_names)
836
851
  data_path = DATA_DIR / 'BIRD_08_16_2012_patm.csv'
837
852
  testdata2 = pd.read_csv(data_path, usecols=range(1, 26), header=1).dropna()
838
- testdata2.index = times[1:48]
853
+ testdata2[['DEC', 'EQT']] = testdata2[['DEC', 'EQT']].shift(tz)
854
+ testdata2 = testdata2[:tz]
855
+ testdata2.index = times[1:end]
839
856
  direct_beam2 = pd.Series(np.where(dawn, Eb2, 0.), index=times).fillna(0.)
840
857
  assert np.allclose(
841
- testdata2['Direct Beam'].where(dusk, 0.), direct_beam2[1:48], rtol=1e-3
858
+ testdata2['Direct Beam'].where(dusk, 0.), direct_beam2[1:end],
859
+ rtol=1e-3
842
860
  )
843
861
  direct_horz2 = pd.Series(np.where(dawn, Ebh2, 0.), index=times).fillna(0.)
844
862
  assert np.allclose(
845
- testdata2['Direct Hz'].where(dusk, 0.), direct_horz2[1:48], rtol=1e-3
863
+ testdata2['Direct Hz'].where(dusk, 0.), direct_horz2[1:end], rtol=1e-3
846
864
  )
847
865
  global_horz2 = pd.Series(np.where(dawn, Gh2, 0.), index=times).fillna(0.)
848
866
  assert np.allclose(
849
- testdata2['Global Hz'].where(dusk, 0.), global_horz2[1:48], rtol=1e-3
867
+ testdata2['Global Hz'].where(dusk, 0.), global_horz2[1:end], rtol=1e-3
850
868
  )
851
869
  diffuse_horz2 = pd.Series(np.where(dawn, Dh2, 0.), index=times).fillna(0.)
852
870
  assert np.allclose(
853
- testdata2['Dif Hz'].where(dusk, 0.), diffuse_horz2[1:48], rtol=1e-3
871
+ testdata2['Dif Hz'].where(dusk, 0.), diffuse_horz2[1:end], rtol=1e-3
854
872
  )
855
873
  # test scalars just at noon
856
874
  # XXX: calculations start at 12am so noon is at index = 12