pvlib 0.11.2__py3-none-any.whl → 0.12.0__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 (139) hide show
  1. pvlib/__init__.py +1 -0
  2. pvlib/atmosphere.py +0 -9
  3. pvlib/bifacial/infinite_sheds.py +4 -3
  4. pvlib/bifacial/utils.py +2 -1
  5. pvlib/iotools/psm3.py +1 -1
  6. pvlib/iotools/pvgis.py +10 -2
  7. pvlib/iotools/tmy.py +3 -69
  8. pvlib/irradiance.py +14 -0
  9. pvlib/location.py +73 -33
  10. pvlib/modelchain.py +18 -35
  11. pvlib/pvsystem.py +7 -10
  12. pvlib/snow.py +64 -28
  13. pvlib/spectrum/__init__.py +0 -1
  14. pvlib/spectrum/irradiance.py +0 -63
  15. pvlib/spectrum/mismatch.py +3 -3
  16. pvlib/tools.py +6 -5
  17. {pvlib-0.11.2.dist-info → pvlib-0.12.0.dist-info}/METADATA +5 -3
  18. pvlib-0.12.0.dist-info/RECORD +75 -0
  19. {pvlib-0.11.2.dist-info → pvlib-0.12.0.dist-info}/WHEEL +1 -1
  20. pvlib/data/BIRD_08_16_2012.csv +0 -8761
  21. pvlib/data/BIRD_08_16_2012_patm.csv +0 -8761
  22. pvlib/data/Burlington, United States SolarAnywhere Time Series 2021 Lat_44_465 Lon_-73_205 TMY3 format.csv +0 -8762
  23. pvlib/data/Burlington, United States SolarAnywhere Time Series 20210101 to 20210103 Lat_44_4675 Lon_-73_2075 SA format.csv +0 -578
  24. pvlib/data/Burlington, United States SolarAnywhere Typical GHI Year Lat_44_465 Lon_-73_205 SA format.csv +0 -74
  25. pvlib/data/CPS SCH275KTL-DO-US-800-250kW_275kVA_1.OND +0 -146
  26. pvlib/data/CRNS0101-05-2019-AZ_Tucson_11_W.txt +0 -4
  27. pvlib/data/CRN_with_problems.txt +0 -3
  28. pvlib/data/ET-M772BH550GL.PAN +0 -75
  29. pvlib/data/NLD_Amsterdam062400_IWEC.epw +0 -8768
  30. pvlib/data/PVsyst_demo.csv +0 -10757
  31. pvlib/data/PVsyst_demo_model.csv +0 -3588
  32. pvlib/data/SRML-day-EUPO1801.txt +0 -1441
  33. pvlib/data/abq19056.dat +0 -6
  34. pvlib/data/bishop88_numerical_precision.csv +0 -101
  35. pvlib/data/bsrn-lr0100-pay0616.dat +0 -86901
  36. pvlib/data/bsrn-pay0616.dat.gz +0 -0
  37. pvlib/data/cams_mcclear_1min_verbose.csv +0 -60
  38. pvlib/data/cams_mcclear_monthly.csv +0 -42
  39. pvlib/data/cams_radiation_1min_verbose.csv +0 -72
  40. pvlib/data/cams_radiation_monthly.csv +0 -47
  41. pvlib/data/detect_clearsky_data.csv +0 -35
  42. pvlib/data/detect_clearsky_threshold_data.csv +0 -126
  43. pvlib/data/greensboro_kimber_soil_manwash.dat +0 -8761
  44. pvlib/data/greensboro_kimber_soil_nowash.dat +0 -8761
  45. pvlib/data/inverter_fit_snl_meas.csv +0 -127
  46. pvlib/data/inverter_fit_snl_sim.csv +0 -19
  47. pvlib/data/ivtools_numdiff.csv +0 -52
  48. pvlib/data/midc_20181014.txt +0 -1441
  49. pvlib/data/midc_raw_20181018.txt +0 -1441
  50. pvlib/data/midc_raw_short_header_20191115.txt +0 -1441
  51. pvlib/data/msn19056.dat +0 -6
  52. pvlib/data/precise_iv_curves1.json +0 -10251
  53. pvlib/data/precise_iv_curves2.json +0 -10251
  54. pvlib/data/precise_iv_curves_parameter_sets1.csv +0 -33
  55. pvlib/data/precise_iv_curves_parameter_sets2.csv +0 -33
  56. pvlib/data/pvgis_hourly_Timeseries_45.000_8.000_SA2_10kWp_CIS_5_2a_2013_2014.json +0 -1
  57. pvlib/data/pvgis_hourly_Timeseries_45.000_8.000_SA_30deg_0deg_2016_2016.csv +0 -35
  58. pvlib/data/pvgis_tmy_meta.json +0 -32
  59. pvlib/data/pvgis_tmy_test.csv +0 -8761
  60. pvlib/data/pvwatts_8760_rackmount.csv +0 -8779
  61. pvlib/data/pvwatts_8760_roofmount.csv +0 -8779
  62. pvlib/data/singleaxis_tracker_wslope.csv +0 -8761
  63. pvlib/data/spectrl2_example_spectra.csv +0 -123
  64. pvlib/data/surfrad-slv16001.dat +0 -1442
  65. pvlib/data/test_psm3_2017.csv +0 -17521
  66. pvlib/data/test_psm3_2019_5min.csv +0 -289
  67. pvlib/data/test_psm3_tmy-2017.csv +0 -8761
  68. pvlib/data/test_read_psm3.csv +0 -17523
  69. pvlib/data/test_read_pvgis_horizon.csv +0 -49
  70. pvlib/data/tmy_45.000_8.000_2005_2023.csv +0 -8789
  71. pvlib/data/tmy_45.000_8.000_2005_2023.epw +0 -8768
  72. pvlib/data/tmy_45.000_8.000_2005_2023.json +0 -1
  73. pvlib/data/tmy_45.000_8.000_2005_2023.txt +0 -8761
  74. pvlib/data/tmy_45.000_8.000_userhorizon.json +0 -1
  75. pvlib/spa_c_files/README.md +0 -81
  76. pvlib/spa_c_files/cspa_py.pxd +0 -43
  77. pvlib/spa_c_files/spa_py.pyx +0 -30
  78. pvlib/tests/__init__.py +0 -0
  79. pvlib/tests/bifacial/__init__.py +0 -0
  80. pvlib/tests/bifacial/test_infinite_sheds.py +0 -317
  81. pvlib/tests/bifacial/test_losses_models.py +0 -54
  82. pvlib/tests/bifacial/test_pvfactors.py +0 -82
  83. pvlib/tests/bifacial/test_utils.py +0 -192
  84. pvlib/tests/conftest.py +0 -476
  85. pvlib/tests/iotools/__init__.py +0 -0
  86. pvlib/tests/iotools/test_acis.py +0 -213
  87. pvlib/tests/iotools/test_bsrn.py +0 -131
  88. pvlib/tests/iotools/test_crn.py +0 -95
  89. pvlib/tests/iotools/test_epw.py +0 -23
  90. pvlib/tests/iotools/test_midc.py +0 -89
  91. pvlib/tests/iotools/test_panond.py +0 -32
  92. pvlib/tests/iotools/test_psm3.py +0 -198
  93. pvlib/tests/iotools/test_pvgis.py +0 -644
  94. pvlib/tests/iotools/test_sodapro.py +0 -298
  95. pvlib/tests/iotools/test_solaranywhere.py +0 -287
  96. pvlib/tests/iotools/test_solargis.py +0 -68
  97. pvlib/tests/iotools/test_solcast.py +0 -324
  98. pvlib/tests/iotools/test_solrad.py +0 -152
  99. pvlib/tests/iotools/test_srml.py +0 -124
  100. pvlib/tests/iotools/test_surfrad.py +0 -75
  101. pvlib/tests/iotools/test_tmy.py +0 -133
  102. pvlib/tests/ivtools/__init__.py +0 -0
  103. pvlib/tests/ivtools/test_sde.py +0 -230
  104. pvlib/tests/ivtools/test_sdm.py +0 -429
  105. pvlib/tests/ivtools/test_utils.py +0 -173
  106. pvlib/tests/spectrum/__init__.py +0 -0
  107. pvlib/tests/spectrum/conftest.py +0 -40
  108. pvlib/tests/spectrum/test_irradiance.py +0 -138
  109. pvlib/tests/spectrum/test_mismatch.py +0 -304
  110. pvlib/tests/spectrum/test_response.py +0 -124
  111. pvlib/tests/spectrum/test_spectrl2.py +0 -72
  112. pvlib/tests/test__deprecation.py +0 -97
  113. pvlib/tests/test_albedo.py +0 -84
  114. pvlib/tests/test_atmosphere.py +0 -351
  115. pvlib/tests/test_clearsky.py +0 -884
  116. pvlib/tests/test_conftest.py +0 -37
  117. pvlib/tests/test_iam.py +0 -555
  118. pvlib/tests/test_inverter.py +0 -213
  119. pvlib/tests/test_irradiance.py +0 -1487
  120. pvlib/tests/test_location.py +0 -356
  121. pvlib/tests/test_modelchain.py +0 -2020
  122. pvlib/tests/test_numerical_precision.py +0 -124
  123. pvlib/tests/test_pvarray.py +0 -71
  124. pvlib/tests/test_pvsystem.py +0 -2511
  125. pvlib/tests/test_scaling.py +0 -207
  126. pvlib/tests/test_shading.py +0 -391
  127. pvlib/tests/test_singlediode.py +0 -608
  128. pvlib/tests/test_snow.py +0 -212
  129. pvlib/tests/test_soiling.py +0 -230
  130. pvlib/tests/test_solarposition.py +0 -966
  131. pvlib/tests/test_spa.py +0 -454
  132. pvlib/tests/test_temperature.py +0 -470
  133. pvlib/tests/test_tools.py +0 -146
  134. pvlib/tests/test_tracking.py +0 -474
  135. pvlib/tests/test_transformer.py +0 -60
  136. pvlib-0.11.2.dist-info/RECORD +0 -191
  137. {pvlib-0.11.2.dist-info → pvlib-0.12.0.dist-info/licenses}/AUTHORS.md +0 -0
  138. {pvlib-0.11.2.dist-info → pvlib-0.12.0.dist-info/licenses}/LICENSE +0 -0
  139. {pvlib-0.11.2.dist-info → pvlib-0.12.0.dist-info}/top_level.txt +0 -0
@@ -1,966 +0,0 @@
1
- import calendar
2
- import datetime
3
- import warnings
4
-
5
- import numpy as np
6
- import pandas as pd
7
-
8
- from .conftest import assert_frame_equal, assert_series_equal
9
- from numpy.testing import assert_allclose
10
- import pytest
11
- import pytz
12
-
13
- from pvlib.location import Location
14
- from pvlib import solarposition, spa
15
-
16
- from .conftest import (
17
- requires_ephem, requires_spa_c, requires_numba, requires_pandas_2_0
18
- )
19
-
20
- # setup times and locations to be tested.
21
- times = pd.date_range(start=datetime.datetime(2014, 6, 24),
22
- end=datetime.datetime(2014, 6, 26), freq='15min')
23
-
24
- tus = Location(32.2, -111, 'US/Arizona', 700) # no DST issues possible
25
- times_localized = times.tz_localize(tus.tz)
26
-
27
- tol = 5
28
-
29
-
30
- @pytest.fixture()
31
- def expected_solpos_multi():
32
- return pd.DataFrame({'elevation': [39.872046, 39.505196],
33
- 'apparent_zenith': [50.111622, 50.478260],
34
- 'azimuth': [194.340241, 194.311132],
35
- 'apparent_elevation': [39.888378, 39.521740]},
36
- index=['2003-10-17T12:30:30Z', '2003-10-18T12:30:30Z'])
37
-
38
-
39
- @pytest.fixture()
40
- def expected_rise_set_spa():
41
- # for Golden, CO, from NREL SPA website
42
- times = pd.DatetimeIndex([datetime.datetime(2015, 1, 2),
43
- datetime.datetime(2015, 8, 2),
44
- ]).tz_localize('MST')
45
- sunrise = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 7, 21, 55),
46
- datetime.datetime(2015, 8, 2, 5, 0, 27)
47
- ]).tz_localize('MST').tolist()
48
- sunset = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 16, 47, 43),
49
- datetime.datetime(2015, 8, 2, 19, 13, 58)
50
- ]).tz_localize('MST').tolist()
51
- transit = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 12, 4, 45),
52
- datetime.datetime(2015, 8, 2, 12, 6, 58)
53
- ]).tz_localize('MST').tolist()
54
- return pd.DataFrame({'sunrise': sunrise,
55
- 'sunset': sunset,
56
- 'transit': transit},
57
- index=times)
58
-
59
-
60
- @pytest.fixture()
61
- def expected_rise_set_ephem():
62
- # for Golden, CO, from USNO websites
63
- times = pd.DatetimeIndex([datetime.datetime(2015, 1, 1),
64
- datetime.datetime(2015, 1, 2),
65
- datetime.datetime(2015, 1, 3),
66
- datetime.datetime(2015, 8, 2),
67
- ]).tz_localize('MST')
68
- sunrise = pd.DatetimeIndex([datetime.datetime(2015, 1, 1, 7, 22, 0),
69
- datetime.datetime(2015, 1, 2, 7, 22, 0),
70
- datetime.datetime(2015, 1, 3, 7, 22, 0),
71
- datetime.datetime(2015, 8, 2, 5, 0, 0)
72
- ]).tz_localize('MST').tolist()
73
- sunset = pd.DatetimeIndex([datetime.datetime(2015, 1, 1, 16, 47, 0),
74
- datetime.datetime(2015, 1, 2, 16, 48, 0),
75
- datetime.datetime(2015, 1, 3, 16, 49, 0),
76
- datetime.datetime(2015, 8, 2, 19, 13, 0)
77
- ]).tz_localize('MST').tolist()
78
- transit = pd.DatetimeIndex([datetime.datetime(2015, 1, 1, 12, 4, 0),
79
- datetime.datetime(2015, 1, 2, 12, 5, 0),
80
- datetime.datetime(2015, 1, 3, 12, 5, 0),
81
- datetime.datetime(2015, 8, 2, 12, 7, 0)
82
- ]).tz_localize('MST').tolist()
83
- return pd.DataFrame({'sunrise': sunrise,
84
- 'sunset': sunset,
85
- 'transit': transit},
86
- index=times)
87
-
88
-
89
- # the physical tests are run at the same time as the NREL SPA test.
90
- # pyephem reproduces the NREL result to 2 decimal places.
91
- # this doesn't mean that one code is better than the other.
92
-
93
- @requires_spa_c
94
- def test_spa_c_physical(expected_solpos, golden_mst):
95
- times = pd.date_range(datetime.datetime(2003, 10, 17, 12, 30, 30),
96
- periods=1, freq='D', tz=golden_mst.tz)
97
- ephem_data = solarposition.spa_c(times, golden_mst.latitude,
98
- golden_mst.longitude,
99
- pressure=82000,
100
- temperature=11)
101
- expected_solpos.index = times
102
- assert_frame_equal(expected_solpos, ephem_data[expected_solpos.columns])
103
-
104
-
105
- @requires_spa_c
106
- def test_spa_c_physical_dst(expected_solpos, golden):
107
- times = pd.date_range(datetime.datetime(2003, 10, 17, 13, 30, 30),
108
- periods=1, freq='D', tz=golden.tz)
109
- ephem_data = solarposition.spa_c(times, golden.latitude,
110
- golden.longitude,
111
- pressure=82000,
112
- temperature=11)
113
- expected_solpos.index = times
114
- assert_frame_equal(expected_solpos, ephem_data[expected_solpos.columns])
115
-
116
-
117
- def test_spa_python_numpy_physical(expected_solpos, golden_mst):
118
- times = pd.date_range(datetime.datetime(2003, 10, 17, 12, 30, 30),
119
- periods=1, freq='D', tz=golden_mst.tz)
120
- ephem_data = solarposition.spa_python(times, golden_mst.latitude,
121
- golden_mst.longitude,
122
- pressure=82000,
123
- temperature=11, delta_t=67,
124
- atmos_refract=0.5667,
125
- how='numpy')
126
- expected_solpos.index = times
127
- assert_frame_equal(expected_solpos, ephem_data[expected_solpos.columns])
128
-
129
-
130
- def test_spa_python_numpy_physical_dst(expected_solpos, golden):
131
- times = pd.date_range(datetime.datetime(2003, 10, 17, 13, 30, 30),
132
- periods=1, freq='D', tz=golden.tz)
133
- ephem_data = solarposition.spa_python(times, golden.latitude,
134
- golden.longitude,
135
- pressure=82000,
136
- temperature=11, delta_t=67,
137
- atmos_refract=0.5667,
138
- how='numpy')
139
- expected_solpos.index = times
140
- assert_frame_equal(expected_solpos, ephem_data[expected_solpos.columns])
141
-
142
-
143
- @pytest.mark.parametrize('delta_t', [65.0, None, np.array([65, 65])])
144
- def test_sun_rise_set_transit_spa(expected_rise_set_spa, golden, delta_t):
145
- # solution from NREL SAP web calculator
146
- south = Location(-35.0, 0.0, tz='UTC')
147
- times = pd.DatetimeIndex([datetime.datetime(1996, 7, 5, 0),
148
- datetime.datetime(2004, 12, 4, 0)]
149
- ).tz_localize('UTC')
150
- sunrise = pd.DatetimeIndex([datetime.datetime(1996, 7, 5, 7, 8, 15),
151
- datetime.datetime(2004, 12, 4, 4, 38, 57)]
152
- ).tz_localize('UTC').tolist()
153
- sunset = pd.DatetimeIndex([datetime.datetime(1996, 7, 5, 17, 1, 4),
154
- datetime.datetime(2004, 12, 4, 19, 2, 3)]
155
- ).tz_localize('UTC').tolist()
156
- transit = pd.DatetimeIndex([datetime.datetime(1996, 7, 5, 12, 4, 36),
157
- datetime.datetime(2004, 12, 4, 11, 50, 22)]
158
- ).tz_localize('UTC').tolist()
159
- frame = pd.DataFrame({'sunrise': sunrise,
160
- 'sunset': sunset,
161
- 'transit': transit}, index=times)
162
-
163
- result = solarposition.sun_rise_set_transit_spa(times, south.latitude,
164
- south.longitude,
165
- delta_t=delta_t)
166
- result_rounded = pd.DataFrame(index=result.index)
167
- # need to iterate because to_datetime does not accept 2D data
168
- # the rounding fails on pandas < 0.17
169
- for col, data in result.items():
170
- result_rounded[col] = data.dt.round('1s')
171
-
172
- assert_frame_equal(frame, result_rounded)
173
-
174
- # test for Golden, CO compare to NREL SPA
175
- result = solarposition.sun_rise_set_transit_spa(
176
- expected_rise_set_spa.index, golden.latitude, golden.longitude,
177
- delta_t=delta_t)
178
-
179
- # round to nearest minute
180
- result_rounded = pd.DataFrame(index=result.index)
181
- # need to iterate because to_datetime does not accept 2D data
182
- for col, data in result.items():
183
- result_rounded[col] = data.dt.round('s').tz_convert('MST')
184
-
185
- assert_frame_equal(expected_rise_set_spa, result_rounded)
186
-
187
-
188
- @requires_ephem
189
- def test_sun_rise_set_transit_ephem(expected_rise_set_ephem, golden):
190
- # test for Golden, CO compare to USNO, using local midnight
191
- result = solarposition.sun_rise_set_transit_ephem(
192
- expected_rise_set_ephem.index, golden.latitude, golden.longitude,
193
- next_or_previous='next', altitude=golden.altitude, pressure=0,
194
- temperature=11, horizon='-0:34')
195
- # round to nearest minute
196
- result_rounded = pd.DataFrame(index=result.index)
197
- for col, data in result.items():
198
- result_rounded[col] = data.dt.round('min').tz_convert('MST')
199
- assert_frame_equal(expected_rise_set_ephem, result_rounded)
200
-
201
- # test next sunrise/sunset with times
202
- times = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 3, 0, 0),
203
- datetime.datetime(2015, 1, 2, 10, 15, 0),
204
- datetime.datetime(2015, 1, 2, 15, 3, 0),
205
- datetime.datetime(2015, 1, 2, 21, 6, 7)
206
- ]).tz_localize('MST')
207
- expected = pd.DataFrame(index=times,
208
- columns=['sunrise', 'sunset'],
209
- dtype='datetime64[ns]')
210
- idx_sunrise = pd.to_datetime(['2015-01-02', '2015-01-03', '2015-01-03',
211
- '2015-01-03']).tz_localize('MST')
212
- expected['sunrise'] = \
213
- expected_rise_set_ephem.loc[idx_sunrise, 'sunrise'].tolist()
214
- idx_sunset = pd.to_datetime(['2015-01-02', '2015-01-02', '2015-01-02',
215
- '2015-01-03']).tz_localize('MST')
216
- expected['sunset'] = \
217
- expected_rise_set_ephem.loc[idx_sunset, 'sunset'].tolist()
218
- idx_transit = pd.to_datetime(['2015-01-02', '2015-01-02', '2015-01-03',
219
- '2015-01-03']).tz_localize('MST')
220
- expected['transit'] = \
221
- expected_rise_set_ephem.loc[idx_transit, 'transit'].tolist()
222
-
223
- result = solarposition.sun_rise_set_transit_ephem(times,
224
- golden.latitude,
225
- golden.longitude,
226
- next_or_previous='next',
227
- altitude=golden.altitude,
228
- pressure=0,
229
- temperature=11,
230
- horizon='-0:34')
231
- # round to nearest minute
232
- result_rounded = pd.DataFrame(index=result.index)
233
- for col, data in result.items():
234
- result_rounded[col] = data.dt.round('min').tz_convert('MST')
235
- assert_frame_equal(expected, result_rounded)
236
-
237
- # test previous sunrise/sunset with times
238
- times = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 3, 0, 0),
239
- datetime.datetime(2015, 1, 2, 10, 15, 0),
240
- datetime.datetime(2015, 1, 3, 3, 0, 0),
241
- datetime.datetime(2015, 1, 3, 13, 6, 7)
242
- ]).tz_localize('MST')
243
- expected = pd.DataFrame(index=times,
244
- columns=['sunrise', 'sunset'],
245
- dtype='datetime64[ns]')
246
- idx_sunrise = pd.to_datetime(['2015-01-01', '2015-01-02', '2015-01-02',
247
- '2015-01-03']).tz_localize('MST')
248
- expected['sunrise'] = \
249
- expected_rise_set_ephem.loc[idx_sunrise, 'sunrise'].tolist()
250
- idx_sunset = pd.to_datetime(['2015-01-01', '2015-01-01', '2015-01-02',
251
- '2015-01-02']).tz_localize('MST')
252
- expected['sunset'] = \
253
- expected_rise_set_ephem.loc[idx_sunset, 'sunset'].tolist()
254
- idx_transit = pd.to_datetime(['2015-01-01', '2015-01-01', '2015-01-02',
255
- '2015-01-03']).tz_localize('MST')
256
- expected['transit'] = \
257
- expected_rise_set_ephem.loc[idx_transit, 'transit'].tolist()
258
-
259
- result = solarposition.sun_rise_set_transit_ephem(
260
- times,
261
- golden.latitude, golden.longitude, next_or_previous='previous',
262
- altitude=golden.altitude, pressure=0, temperature=11, horizon='-0:34')
263
- # round to nearest minute
264
- result_rounded = pd.DataFrame(index=result.index)
265
- for col, data in result.items():
266
- result_rounded[col] = data.dt.round('min').tz_convert('MST')
267
- assert_frame_equal(expected, result_rounded)
268
-
269
- # test with different timezone
270
- times = times.tz_convert('UTC')
271
- expected = expected.tz_convert('UTC') # resuse result from previous
272
- for col, data in expected.items():
273
- expected[col] = data.dt.tz_convert('UTC')
274
- result = solarposition.sun_rise_set_transit_ephem(
275
- times,
276
- golden.latitude, golden.longitude, next_or_previous='previous',
277
- altitude=golden.altitude, pressure=0, temperature=11, horizon='-0:34')
278
- # round to nearest minute
279
- result_rounded = pd.DataFrame(index=result.index)
280
- for col, data in result.items():
281
- result_rounded[col] = data.dt.round('min').tz_convert(times.tz)
282
- assert_frame_equal(expected, result_rounded)
283
-
284
-
285
- @requires_ephem
286
- def test_sun_rise_set_transit_ephem_error(expected_rise_set_ephem, golden):
287
- with pytest.raises(ValueError):
288
- solarposition.sun_rise_set_transit_ephem(expected_rise_set_ephem.index,
289
- golden.latitude,
290
- golden.longitude,
291
- next_or_previous='other')
292
- tz_naive = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 3, 0, 0)])
293
- with pytest.raises(ValueError):
294
- solarposition.sun_rise_set_transit_ephem(tz_naive,
295
- golden.latitude,
296
- golden.longitude,
297
- next_or_previous='next')
298
-
299
-
300
- @requires_ephem
301
- def test_sun_rise_set_transit_ephem_horizon(golden):
302
- times = pd.DatetimeIndex([datetime.datetime(2016, 1, 3, 0, 0, 0)
303
- ]).tz_localize('MST')
304
- # center of sun disk
305
- center = solarposition.sun_rise_set_transit_ephem(
306
- times,
307
- latitude=golden.latitude, longitude=golden.longitude)
308
- edge = solarposition.sun_rise_set_transit_ephem(
309
- times,
310
- latitude=golden.latitude, longitude=golden.longitude, horizon='-0:34')
311
- result_rounded = (edge['sunrise'] - center['sunrise']).dt.round('min')
312
-
313
- sunrise_delta = datetime.datetime(2016, 1, 3, 7, 17, 11) - \
314
- datetime.datetime(2016, 1, 3, 7, 21, 33)
315
- expected = pd.Series(index=times,
316
- data=[sunrise_delta],
317
- name='sunrise').dt.round('min')
318
- assert_series_equal(expected, result_rounded)
319
-
320
-
321
- @requires_ephem
322
- def test_pyephem_physical(expected_solpos, golden_mst):
323
- times = pd.date_range(datetime.datetime(2003, 10, 17, 12, 30, 30),
324
- periods=1, freq='D', tz=golden_mst.tz)
325
- ephem_data = solarposition.pyephem(times, golden_mst.latitude,
326
- golden_mst.longitude, pressure=82000,
327
- temperature=11)
328
- expected_solpos.index = times
329
- assert_frame_equal(expected_solpos.round(2),
330
- ephem_data[expected_solpos.columns].round(2))
331
-
332
-
333
- @requires_ephem
334
- def test_pyephem_physical_dst(expected_solpos, golden):
335
- times = pd.date_range(datetime.datetime(2003, 10, 17, 13, 30, 30),
336
- periods=1, freq='D', tz=golden.tz)
337
- ephem_data = solarposition.pyephem(times, golden.latitude,
338
- golden.longitude, pressure=82000,
339
- temperature=11)
340
- expected_solpos.index = times
341
- assert_frame_equal(expected_solpos.round(2),
342
- ephem_data[expected_solpos.columns].round(2))
343
-
344
-
345
- @requires_ephem
346
- def test_calc_time():
347
- import pytz
348
- import math
349
- # validation from USNO solar position calculator online
350
-
351
- epoch = datetime.datetime(1970, 1, 1)
352
- epoch_dt = pytz.utc.localize(epoch)
353
-
354
- loc = tus
355
- loc.pressure = 0
356
- actual_time = pytz.timezone(loc.tz).localize(
357
- datetime.datetime(2014, 10, 10, 8, 30))
358
- lb = pytz.timezone(loc.tz).localize(datetime.datetime(2014, 10, 10, tol))
359
- ub = pytz.timezone(loc.tz).localize(datetime.datetime(2014, 10, 10, 10))
360
- alt = solarposition.calc_time(lb, ub, loc.latitude, loc.longitude,
361
- 'alt', math.radians(24.7))
362
- az = solarposition.calc_time(lb, ub, loc.latitude, loc.longitude,
363
- 'az', math.radians(116.3))
364
- actual_timestamp = (actual_time - epoch_dt).total_seconds()
365
-
366
- assert_allclose((alt.replace(second=0, microsecond=0) -
367
- epoch_dt).total_seconds(), actual_timestamp)
368
- assert_allclose((az.replace(second=0, microsecond=0) -
369
- epoch_dt).total_seconds(), actual_timestamp)
370
-
371
-
372
- @requires_ephem
373
- def test_earthsun_distance():
374
- times = pd.date_range(datetime.datetime(2003, 10, 17, 13, 30, 30),
375
- periods=1, freq='D')
376
- distance = solarposition.pyephem_earthsun_distance(times).values[0]
377
- assert_allclose(1, distance, atol=0.1)
378
-
379
-
380
- def test_ephemeris_physical(expected_solpos, golden_mst):
381
- times = pd.date_range(datetime.datetime(2003, 10, 17, 12, 30, 30),
382
- periods=1, freq='D', tz=golden_mst.tz)
383
- ephem_data = solarposition.ephemeris(times, golden_mst.latitude,
384
- golden_mst.longitude,
385
- pressure=82000,
386
- temperature=11)
387
- expected_solpos.index = times
388
- expected_solpos = np.round(expected_solpos, 2)
389
- ephem_data = np.round(ephem_data, 2)
390
- assert_frame_equal(expected_solpos, ephem_data[expected_solpos.columns])
391
-
392
-
393
- def test_ephemeris_physical_dst(expected_solpos, golden):
394
- times = pd.date_range(datetime.datetime(2003, 10, 17, 13, 30, 30),
395
- periods=1, freq='D', tz=golden.tz)
396
- ephem_data = solarposition.ephemeris(times, golden.latitude,
397
- golden.longitude, pressure=82000,
398
- temperature=11)
399
- expected_solpos.index = times
400
- expected_solpos = np.round(expected_solpos, 2)
401
- ephem_data = np.round(ephem_data, 2)
402
- assert_frame_equal(expected_solpos, ephem_data[expected_solpos.columns])
403
-
404
-
405
- def test_ephemeris_physical_no_tz(expected_solpos, golden_mst):
406
- times = pd.date_range(datetime.datetime(2003, 10, 17, 19, 30, 30),
407
- periods=1, freq='D')
408
- ephem_data = solarposition.ephemeris(times, golden_mst.latitude,
409
- golden_mst.longitude,
410
- pressure=82000,
411
- temperature=11)
412
- expected_solpos.index = times
413
- expected_solpos = np.round(expected_solpos, 2)
414
- ephem_data = np.round(ephem_data, 2)
415
- assert_frame_equal(expected_solpos, ephem_data[expected_solpos.columns])
416
-
417
-
418
- def test_get_solarposition_error(golden):
419
- times = pd.date_range(datetime.datetime(2003, 10, 17, 13, 30, 30),
420
- periods=1, freq='D', tz=golden.tz)
421
- with pytest.raises(ValueError):
422
- solarposition.get_solarposition(times, golden.latitude,
423
- golden.longitude,
424
- pressure=82000,
425
- temperature=11,
426
- method='error this')
427
-
428
-
429
- @pytest.mark.parametrize("pressure, expected", [
430
- (82000, 'expected_solpos'),
431
- (90000, pd.DataFrame(
432
- np.array([[39.88997, 50.11003, 194.34024, 39.87205, 14.64151,
433
- 50.12795]]),
434
- columns=['apparent_elevation', 'apparent_zenith', 'azimuth',
435
- 'elevation', 'equation_of_time', 'zenith'],
436
- index=['2003-10-17T12:30:30Z']))
437
- ])
438
- def test_get_solarposition_pressure(
439
- pressure, expected, golden, expected_solpos):
440
- times = pd.date_range(datetime.datetime(2003, 10, 17, 13, 30, 30),
441
- periods=1, freq='D', tz=golden.tz)
442
- ephem_data = solarposition.get_solarposition(times, golden.latitude,
443
- golden.longitude,
444
- pressure=pressure,
445
- temperature=11)
446
- if isinstance(expected, str) and expected == 'expected_solpos':
447
- expected = expected_solpos
448
- this_expected = expected.copy()
449
- this_expected.index = times
450
- this_expected = np.round(this_expected, 5)
451
- ephem_data = np.round(ephem_data, 5)
452
- assert_frame_equal(this_expected, ephem_data[this_expected.columns])
453
-
454
-
455
- @pytest.mark.parametrize("altitude, expected", [
456
- (1830.14, 'expected_solpos'),
457
- (2000, pd.DataFrame(
458
- np.array([[39.88788, 50.11212, 194.34024, 39.87205, 14.64151,
459
- 50.12795]]),
460
- columns=['apparent_elevation', 'apparent_zenith', 'azimuth',
461
- 'elevation', 'equation_of_time', 'zenith'],
462
- index=['2003-10-17T12:30:30Z']))
463
- ])
464
- def test_get_solarposition_altitude(
465
- altitude, expected, golden, expected_solpos):
466
- times = pd.date_range(datetime.datetime(2003, 10, 17, 13, 30, 30),
467
- periods=1, freq='D', tz=golden.tz)
468
- ephem_data = solarposition.get_solarposition(times, golden.latitude,
469
- golden.longitude,
470
- altitude=altitude,
471
- temperature=11)
472
- if isinstance(expected, str) and expected == 'expected_solpos':
473
- expected = expected_solpos
474
- this_expected = expected.copy()
475
- this_expected.index = times
476
- this_expected = np.round(this_expected, 5)
477
- ephem_data = np.round(ephem_data, 5)
478
- assert_frame_equal(this_expected, ephem_data[this_expected.columns])
479
-
480
-
481
- @pytest.mark.parametrize("delta_t, method", [
482
- (None, 'nrel_numba'),
483
- (67.0, 'nrel_numba'),
484
- (np.array([67.0, 67.0]), 'nrel_numba'),
485
- # minimize reloads, with numpy being last
486
- (None, 'nrel_numpy'),
487
- (67.0, 'nrel_numpy'),
488
- (np.array([67.0, 67.0]), 'nrel_numpy'),
489
- ])
490
- def test_get_solarposition_deltat(delta_t, method, expected_solpos_multi,
491
- golden):
492
- times = pd.date_range(datetime.datetime(2003, 10, 17, 13, 30, 30),
493
- periods=2, freq='D', tz=golden.tz)
494
- with warnings.catch_warnings():
495
- # don't warn on method reload
496
- warnings.simplefilter("ignore")
497
- ephem_data = solarposition.get_solarposition(times, golden.latitude,
498
- golden.longitude,
499
- pressure=82000,
500
- delta_t=delta_t,
501
- temperature=11,
502
- method=method)
503
- this_expected = expected_solpos_multi
504
- this_expected.index = times
505
- this_expected = np.round(this_expected, 5)
506
- ephem_data = np.round(ephem_data, 5)
507
- assert_frame_equal(this_expected, ephem_data[this_expected.columns])
508
-
509
-
510
- @pytest.mark.parametrize("method", ['nrel_numba', 'nrel_numpy'])
511
- def test_spa_array_delta_t(method):
512
- # make sure that time-varying delta_t produces different answers
513
- times = pd.to_datetime(["2019-01-01", "2019-01-01"]).tz_localize("UTC")
514
- expected = pd.Series([257.26969492, 257.2701359], index=times)
515
- with warnings.catch_warnings():
516
- # don't warn on method reload
517
- warnings.simplefilter("ignore")
518
- ephem_data = solarposition.get_solarposition(times, 40, -80,
519
- delta_t=np.array([67, 0]),
520
- method=method)
521
-
522
- assert_series_equal(ephem_data['azimuth'], expected, check_names=False)
523
-
524
-
525
- def test_get_solarposition_no_kwargs(expected_solpos, golden):
526
- times = pd.date_range(datetime.datetime(2003, 10, 17, 13, 30, 30),
527
- periods=1, freq='D', tz=golden.tz)
528
- ephem_data = solarposition.get_solarposition(times, golden.latitude,
529
- golden.longitude)
530
- expected_solpos.index = times
531
- expected_solpos = np.round(expected_solpos, 2)
532
- ephem_data = np.round(ephem_data, 2)
533
- assert_frame_equal(expected_solpos, ephem_data[expected_solpos.columns])
534
-
535
-
536
- @requires_ephem
537
- def test_get_solarposition_method_pyephem(expected_solpos, golden):
538
- times = pd.date_range(datetime.datetime(2003, 10, 17, 13, 30, 30),
539
- periods=1, freq='D', tz=golden.tz)
540
- ephem_data = solarposition.get_solarposition(times, golden.latitude,
541
- golden.longitude,
542
- method='pyephem')
543
- expected_solpos.index = times
544
- expected_solpos = np.round(expected_solpos, 2)
545
- ephem_data = np.round(ephem_data, 2)
546
- assert_frame_equal(expected_solpos, ephem_data[expected_solpos.columns])
547
-
548
-
549
- @pytest.mark.parametrize('delta_t', [64.0, None, np.array([64, 64])])
550
- def test_nrel_earthsun_distance(delta_t):
551
- times = pd.DatetimeIndex([datetime.datetime(2015, 1, 2),
552
- datetime.datetime(2015, 8, 2)]
553
- ).tz_localize('MST')
554
- result = solarposition.nrel_earthsun_distance(times, delta_t=delta_t)
555
- expected = pd.Series(np.array([0.983289204601, 1.01486146446]),
556
- index=times)
557
- assert_series_equal(expected, result)
558
-
559
- if np.size(delta_t) == 1: # skip the array delta_t
560
- times = datetime.datetime(2015, 1, 2)
561
- result = solarposition.nrel_earthsun_distance(times, delta_t=delta_t)
562
- expected = pd.Series(np.array([0.983289204601]),
563
- index=pd.DatetimeIndex([times, ]))
564
- assert_series_equal(expected, result)
565
-
566
-
567
- def test_equation_of_time():
568
- times = pd.date_range(start="1/1/2015 0:00", end="12/31/2015 23:00",
569
- freq="h")
570
- output = solarposition.spa_python(times, 37.8, -122.25, 100)
571
- eot = output['equation_of_time']
572
- eot_rng = eot.max() - eot.min() # range of values, around 30 minutes
573
- eot_1 = solarposition.equation_of_time_spencer71(times.dayofyear)
574
- eot_2 = solarposition.equation_of_time_pvcdrom(times.dayofyear)
575
- assert np.allclose(eot_1 / eot_rng, eot / eot_rng, atol=0.3) # spencer
576
- assert np.allclose(eot_2 / eot_rng, eot / eot_rng, atol=0.4) # pvcdrom
577
-
578
-
579
- def test_declination():
580
- times = pd.date_range(start="1/1/2015 0:00", end="12/31/2015 23:00",
581
- freq="h")
582
- atmos_refract = 0.5667
583
- delta_t = spa.calculate_deltat(times.year, times.month)
584
- unixtime = np.array([calendar.timegm(t.timetuple()) for t in times])
585
- _, _, declination = spa.solar_position(unixtime, 37.8, -122.25, 100,
586
- 1013.25, 25, delta_t, atmos_refract,
587
- sst=True)
588
- declination = np.deg2rad(declination)
589
- declination_rng = declination.max() - declination.min()
590
- declination_1 = solarposition.declination_cooper69(times.dayofyear)
591
- declination_2 = solarposition.declination_spencer71(times.dayofyear)
592
- a, b = declination_1 / declination_rng, declination / declination_rng
593
- assert np.allclose(a, b, atol=0.03) # cooper
594
- a, b = declination_2 / declination_rng, declination / declination_rng
595
- assert np.allclose(a, b, atol=0.02) # spencer
596
-
597
-
598
- def test_analytical_zenith():
599
- times = pd.date_range(start="1/1/2015 0:00", end="12/31/2015 23:00",
600
- freq="h").tz_localize('Etc/GMT+8')
601
- times_utc = times.tz_convert('UTC')
602
- lat, lon = 37.8, -122.25
603
- lat_rad = np.deg2rad(lat)
604
- output = solarposition.spa_python(times, lat, lon, 100)
605
- solar_zenith = np.deg2rad(output['zenith']) # spa
606
- # spencer
607
- eot = solarposition.equation_of_time_spencer71(times_utc.dayofyear)
608
- hour_angle = np.deg2rad(solarposition.hour_angle(times, lon, eot))
609
- decl = solarposition.declination_spencer71(times_utc.dayofyear)
610
- zenith_1 = solarposition.solar_zenith_analytical(lat_rad, hour_angle, decl)
611
- # pvcdrom and cooper
612
- eot = solarposition.equation_of_time_pvcdrom(times_utc.dayofyear)
613
- hour_angle = np.deg2rad(solarposition.hour_angle(times, lon, eot))
614
- decl = solarposition.declination_cooper69(times_utc.dayofyear)
615
- zenith_2 = solarposition.solar_zenith_analytical(lat_rad, hour_angle, decl)
616
- assert np.allclose(zenith_1, solar_zenith, atol=0.015)
617
- assert np.allclose(zenith_2, solar_zenith, atol=0.025)
618
-
619
-
620
- def test_analytical_azimuth():
621
- times = pd.date_range(start="1/1/2015 0:00", end="12/31/2015 23:00",
622
- freq="h").tz_localize('Etc/GMT+8')
623
- times_utc = times.tz_convert('UTC')
624
- lat, lon = 37.8, -122.25
625
- lat_rad = np.deg2rad(lat)
626
- output = solarposition.spa_python(times, lat, lon, 100)
627
- solar_azimuth = np.deg2rad(output['azimuth']) # spa
628
- solar_zenith = np.deg2rad(output['zenith'])
629
- # spencer
630
- eot = solarposition.equation_of_time_spencer71(times_utc.dayofyear)
631
- hour_angle = np.deg2rad(solarposition.hour_angle(times, lon, eot))
632
- decl = solarposition.declination_spencer71(times_utc.dayofyear)
633
- zenith = solarposition.solar_zenith_analytical(lat_rad, hour_angle, decl)
634
- azimuth_1 = solarposition.solar_azimuth_analytical(lat_rad, hour_angle,
635
- decl, zenith)
636
- # pvcdrom and cooper
637
- eot = solarposition.equation_of_time_pvcdrom(times_utc.dayofyear)
638
- hour_angle = np.deg2rad(solarposition.hour_angle(times, lon, eot))
639
- decl = solarposition.declination_cooper69(times_utc.dayofyear)
640
- zenith = solarposition.solar_zenith_analytical(lat_rad, hour_angle, decl)
641
- azimuth_2 = solarposition.solar_azimuth_analytical(lat_rad, hour_angle,
642
- decl, zenith)
643
-
644
- idx = np.where(solar_zenith < np.pi/2)
645
- assert np.allclose(azimuth_1[idx], solar_azimuth.values[idx], atol=0.01)
646
- assert np.allclose(azimuth_2[idx], solar_azimuth.values[idx], atol=0.017)
647
-
648
- # test for NaN values at boundary conditions (PR #431)
649
- test_angles = np.radians(np.array(
650
- [[ 0., -180., -20.],
651
- [ 0., 0., -5.],
652
- [ 0., 0., 0.],
653
- [ 0., 0., 15.],
654
- [ 0., 180., 20.],
655
- [ 30., 0., -20.],
656
- [ 30., 0., -5.],
657
- [ 30., 0., 0.],
658
- [ 30., 180., 5.],
659
- [ 30., 0., 10.],
660
- [ -30., 0., -20.],
661
- [ -30., 0., -15.],
662
- [ -30., 0., 0.],
663
- [ -30., -180., 5.],
664
- [ -30., 180., 10.]]))
665
-
666
- zeniths = solarposition.solar_zenith_analytical(*test_angles.T)
667
- azimuths = solarposition.solar_azimuth_analytical(*test_angles.T,
668
- zenith=zeniths)
669
-
670
- assert not np.isnan(azimuths).any()
671
-
672
-
673
- def test_hour_angle():
674
- """
675
- Test conversion from hours to hour angles in degrees given the following
676
- inputs from NREL SPA calculator at Golden, CO
677
- date,times,eot,sunrise,sunset
678
- 1/2/2015,7:21:55,-3.935172,-70.699400,70.512721
679
- 1/2/2015,16:47:43,-4.117227,-70.699400,70.512721
680
- 1/2/2015,12:04:45,-4.026295,-70.699400,70.512721
681
- """
682
- longitude = -105.1786 # degrees
683
- times = pd.DatetimeIndex([
684
- '2015-01-02 07:21:55.2132',
685
- '2015-01-02 16:47:42.9828',
686
- '2015-01-02 12:04:44.6340'
687
- ]).tz_localize('Etc/GMT+7')
688
- eot = np.array([-3.935172, -4.117227, -4.026295])
689
- hourangle = solarposition.hour_angle(times, longitude, eot)
690
- expected = (-70.682338, 70.72118825000001, 0.000801250)
691
- # FIXME: there are differences from expected NREL SPA calculator values
692
- # sunrise: 4 seconds, sunset: 48 seconds, transit: 0.2 seconds
693
- # but the differences may be due to other SPA input parameters
694
- assert np.allclose(hourangle, expected)
695
-
696
- hours = solarposition._hour_angle_to_hours(
697
- times, hourangle, longitude, eot)
698
- result = solarposition._times_to_hours_after_local_midnight(times)
699
- assert np.allclose(result, hours)
700
-
701
- result = solarposition._local_times_from_hours_since_midnight(times, hours)
702
- assert result.equals(times)
703
-
704
- times = times.tz_convert(None)
705
- with pytest.raises(ValueError):
706
- solarposition.hour_angle(times, longitude, eot)
707
- with pytest.raises(ValueError):
708
- solarposition._hour_angle_to_hours(times, hourangle, longitude, eot)
709
- with pytest.raises(ValueError):
710
- solarposition._times_to_hours_after_local_midnight(times)
711
- with pytest.raises(ValueError):
712
- solarposition._local_times_from_hours_since_midnight(times, hours)
713
-
714
-
715
- def test_hour_angle_with_tricky_timezones():
716
- # GH 2132
717
- # tests timezones that have a DST shift at midnight
718
-
719
- eot = np.array([-3.935172, -4.117227, -4.026295, -4.026295])
720
-
721
- longitude = 70.6693
722
- times = pd.DatetimeIndex([
723
- '2014-09-06 23:00:00',
724
- '2014-09-07 00:00:00',
725
- '2014-09-07 01:00:00',
726
- '2014-09-07 02:00:00',
727
- ]).tz_localize('America/Santiago', nonexistent='shift_forward')
728
-
729
- with pytest.raises(pytz.exceptions.NonExistentTimeError):
730
- times.normalize()
731
-
732
- # should not raise `pytz.exceptions.NonExistentTimeError`
733
- solarposition.hour_angle(times, longitude, eot)
734
-
735
- longitude = 82.3666
736
- times = pd.DatetimeIndex([
737
- '2014-11-01 23:00:00',
738
- '2014-11-02 00:00:00',
739
- '2014-11-02 01:00:00',
740
- '2014-11-02 02:00:00',
741
- ]).tz_localize('America/Havana', ambiguous=[True, True, False, False])
742
-
743
- with pytest.raises(pytz.exceptions.AmbiguousTimeError):
744
- solarposition.hour_angle(times, longitude, eot)
745
-
746
-
747
- def test_sun_rise_set_transit_geometric(expected_rise_set_spa, golden_mst):
748
- """Test geometric calculations for sunrise, sunset, and transit times"""
749
- times = expected_rise_set_spa.index
750
- times_utc = times.tz_convert('UTC')
751
- latitude = golden_mst.latitude
752
- longitude = golden_mst.longitude
753
- eot = solarposition.equation_of_time_spencer71(
754
- times_utc.dayofyear) # minutes
755
- decl = solarposition.declination_spencer71(times_utc.dayofyear) # radians
756
- with pytest.raises(ValueError):
757
- solarposition.sun_rise_set_transit_geometric(
758
- times.tz_convert(None), latitude=latitude, longitude=longitude,
759
- declination=decl, equation_of_time=eot)
760
- sr, ss, st = solarposition.sun_rise_set_transit_geometric(
761
- times, latitude=latitude, longitude=longitude, declination=decl,
762
- equation_of_time=eot)
763
- # sunrise: 2015-01-02 07:26:39.763224487, 2015-08-02 05:04:35.688533801
764
- # sunset: 2015-01-02 16:41:29.951096777, 2015-08-02 19:09:46.597355085
765
- # transit: 2015-01-02 12:04:04.857160632, 2015-08-02 12:07:11.142944443
766
- test_sunrise = solarposition._times_to_hours_after_local_midnight(sr)
767
- test_sunset = solarposition._times_to_hours_after_local_midnight(ss)
768
- test_transit = solarposition._times_to_hours_after_local_midnight(st)
769
- # convert expected SPA sunrise, sunset, transit to local datetime indices
770
- expected_sunrise = pd.DatetimeIndex(expected_rise_set_spa.sunrise.values,
771
- tz='UTC').tz_convert(golden_mst.tz)
772
- expected_sunset = pd.DatetimeIndex(expected_rise_set_spa.sunset.values,
773
- tz='UTC').tz_convert(golden_mst.tz)
774
- expected_transit = pd.DatetimeIndex(expected_rise_set_spa.transit.values,
775
- tz='UTC').tz_convert(golden_mst.tz)
776
- # convert expected times to hours since midnight as arrays of floats
777
- expected_sunrise = solarposition._times_to_hours_after_local_midnight(
778
- expected_sunrise)
779
- expected_sunset = solarposition._times_to_hours_after_local_midnight(
780
- expected_sunset)
781
- expected_transit = solarposition._times_to_hours_after_local_midnight(
782
- expected_transit)
783
- # geometric time has about 4-6 minute error compared to SPA sunset/sunrise
784
- expected_sunrise_error = np.array(
785
- [0.07910089555555544, 0.06908014805555496]) # 4.8[min], 4.2[min]
786
- expected_sunset_error = np.array(
787
- [-0.1036246955555562, -0.06983406805555603]) # -6.2[min], -4.2[min]
788
- expected_transit_error = np.array(
789
- [-0.011150788888889096, 0.0036508177777765383]) # -40[sec], 13.3[sec]
790
- assert np.allclose(test_sunrise, expected_sunrise,
791
- atol=np.abs(expected_sunrise_error).max())
792
- assert np.allclose(test_sunset, expected_sunset,
793
- atol=np.abs(expected_sunset_error).max())
794
- assert np.allclose(test_transit, expected_transit,
795
- atol=np.abs(expected_transit_error).max())
796
-
797
-
798
- @pytest.mark.parametrize('tz', [None, 'utc', 'US/Eastern'])
799
- def test__datetime_to_unixtime(tz):
800
- # for pandas < 2.0 where "unit" doesn't exist in pd.date_range. note that
801
- # unit of ns is the only option in pandas<2, and the default in pandas 2.x
802
- times = pd.date_range(start='2019-01-01', freq='h', periods=3, tz=tz)
803
- expected = times.view(np.int64)/10**9
804
- actual = solarposition._datetime_to_unixtime(times)
805
- np.testing.assert_equal(expected, actual)
806
-
807
-
808
- @requires_pandas_2_0
809
- @pytest.mark.parametrize('unit', ['ns', 'us', 's'])
810
- @pytest.mark.parametrize('tz', [None, 'utc', 'US/Eastern'])
811
- def test__datetime_to_unixtime_units(unit, tz):
812
- kwargs = dict(start='2019-01-01', freq='h', periods=3)
813
- times = pd.date_range(**kwargs, unit='ns', tz='UTC')
814
- expected = times.view(np.int64)/10**9
815
-
816
- times = pd.date_range(**kwargs, unit=unit, tz='UTC').tz_convert(tz)
817
- actual = solarposition._datetime_to_unixtime(times)
818
- np.testing.assert_equal(expected, actual)
819
-
820
-
821
- @requires_pandas_2_0
822
- @pytest.mark.parametrize('tz', [None, 'utc', 'US/Eastern'])
823
- @pytest.mark.parametrize('method', [
824
- 'nrel_numpy',
825
- 'ephemeris',
826
- pytest.param('pyephem', marks=requires_ephem),
827
- pytest.param('nrel_numba', marks=requires_numba),
828
- pytest.param('nrel_c', marks=requires_spa_c),
829
- ])
830
- def test_get_solarposition_microsecond_index(method, tz):
831
- # https://github.com/pvlib/pvlib-python/issues/1932
832
-
833
- kwargs = dict(start='2019-01-01', freq='h', periods=24, tz=tz)
834
-
835
- index_ns = pd.date_range(unit='ns', **kwargs)
836
- index_us = pd.date_range(unit='us', **kwargs)
837
-
838
- with warnings.catch_warnings():
839
- # don't warn on method reload
840
- warnings.simplefilter("ignore")
841
-
842
- sp_ns = solarposition.get_solarposition(index_ns, 0, 0, method=method)
843
- sp_us = solarposition.get_solarposition(index_us, 0, 0, method=method)
844
-
845
- assert_frame_equal(sp_ns, sp_us, check_index_type=False)
846
-
847
-
848
- @requires_pandas_2_0
849
- @pytest.mark.parametrize('tz', [None, 'utc', 'US/Eastern'])
850
- def test_nrel_earthsun_distance_microsecond_index(tz):
851
- # https://github.com/pvlib/pvlib-python/issues/1932
852
-
853
- kwargs = dict(start='2019-01-01', freq='h', periods=24, tz=tz)
854
-
855
- index_ns = pd.date_range(unit='ns', **kwargs)
856
- index_us = pd.date_range(unit='us', **kwargs)
857
-
858
- esd_ns = solarposition.nrel_earthsun_distance(index_ns)
859
- esd_us = solarposition.nrel_earthsun_distance(index_us)
860
-
861
- assert_series_equal(esd_ns, esd_us, check_index_type=False)
862
-
863
-
864
- @requires_pandas_2_0
865
- @pytest.mark.parametrize('tz', ['utc', 'US/Eastern'])
866
- def test_hour_angle_microsecond_index(tz):
867
- # https://github.com/pvlib/pvlib-python/issues/1932
868
-
869
- kwargs = dict(start='2019-01-01', freq='h', periods=24, tz=tz)
870
-
871
- index_ns = pd.date_range(unit='ns', **kwargs)
872
- index_us = pd.date_range(unit='us', **kwargs)
873
-
874
- ha_ns = solarposition.hour_angle(index_ns, -80, 0)
875
- ha_us = solarposition.hour_angle(index_us, -80, 0)
876
-
877
- np.testing.assert_equal(ha_ns, ha_us)
878
-
879
-
880
- @requires_pandas_2_0
881
- @pytest.mark.parametrize('tz', ['utc', 'US/Eastern'])
882
- def test_rise_set_transit_spa_microsecond_index(tz):
883
- # https://github.com/pvlib/pvlib-python/issues/1932
884
-
885
- kwargs = dict(start='2019-01-01', freq='h', periods=24, tz=tz)
886
-
887
- index_ns = pd.date_range(unit='ns', **kwargs)
888
- index_us = pd.date_range(unit='us', **kwargs)
889
-
890
- rst_ns = solarposition.sun_rise_set_transit_spa(index_ns, 40, -80)
891
- rst_us = solarposition.sun_rise_set_transit_spa(index_us, 40, -80)
892
-
893
- assert_frame_equal(rst_ns, rst_us, check_index_type=False)
894
-
895
-
896
- @requires_pandas_2_0
897
- @pytest.mark.parametrize('tz', ['utc', 'US/Eastern'])
898
- def test_rise_set_transit_geometric_microsecond_index(tz):
899
- # https://github.com/pvlib/pvlib-python/issues/1932
900
-
901
- kwargs = dict(start='2019-01-01', freq='h', periods=24, tz=tz)
902
-
903
- index_ns = pd.date_range(unit='ns', **kwargs)
904
- index_us = pd.date_range(unit='us', **kwargs)
905
-
906
- args = (40, -80, 0, 0)
907
- rst_ns = solarposition.sun_rise_set_transit_geometric(index_ns, *args)
908
- rst_us = solarposition.sun_rise_set_transit_geometric(index_us, *args)
909
-
910
- for times_ns, times_us in zip(rst_ns, rst_us):
911
- # can't use a fancy assert function here since the units are different
912
- assert all(times_ns == times_us)
913
-
914
-
915
- # put numba tests at end of file to minimize reloading
916
-
917
- @requires_numba
918
- def test_spa_python_numba_physical(expected_solpos, golden_mst):
919
- times = pd.date_range(datetime.datetime(2003, 10, 17, 12, 30, 30),
920
- periods=1, freq='D', tz=golden_mst.tz)
921
- with warnings.catch_warnings():
922
- # don't warn on method reload
923
- # ensure that numpy is the most recently used method so that
924
- # we can use the warns filter below
925
- warnings.simplefilter("ignore")
926
- ephem_data = solarposition.spa_python(times, golden_mst.latitude,
927
- golden_mst.longitude,
928
- pressure=82000,
929
- temperature=11, delta_t=67,
930
- atmos_refract=0.5667,
931
- how='numpy', numthreads=1)
932
- with pytest.warns(UserWarning):
933
- ephem_data = solarposition.spa_python(times, golden_mst.latitude,
934
- golden_mst.longitude,
935
- pressure=82000,
936
- temperature=11, delta_t=67,
937
- atmos_refract=0.5667,
938
- how='numba', numthreads=1)
939
- expected_solpos.index = times
940
- assert_frame_equal(expected_solpos, ephem_data[expected_solpos.columns])
941
-
942
-
943
- @requires_numba
944
- def test_spa_python_numba_physical_dst(expected_solpos, golden):
945
- times = pd.date_range(datetime.datetime(2003, 10, 17, 13, 30, 30),
946
- periods=1, freq='D', tz=golden.tz)
947
-
948
- with warnings.catch_warnings():
949
- # don't warn on method reload
950
- warnings.simplefilter("ignore")
951
- ephem_data = solarposition.spa_python(times, golden.latitude,
952
- golden.longitude, pressure=82000,
953
- temperature=11, delta_t=67,
954
- atmos_refract=0.5667,
955
- how='numba', numthreads=1)
956
- expected_solpos.index = times
957
- assert_frame_equal(expected_solpos, ephem_data[expected_solpos.columns])
958
-
959
- with pytest.warns(UserWarning):
960
- # test that we get a warning when reloading to use numpy only
961
- ephem_data = solarposition.spa_python(times, golden.latitude,
962
- golden.longitude,
963
- pressure=82000,
964
- temperature=11, delta_t=67,
965
- atmos_refract=0.5667,
966
- how='numpy', numthreads=1)