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