pvlib 0.11.2__py3-none-any.whl → 0.12.1a1__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 (147) hide show
  1. pvlib/__init__.py +1 -0
  2. pvlib/atmosphere.py +40 -40
  3. pvlib/bifacial/infinite_sheds.py +4 -3
  4. pvlib/bifacial/utils.py +2 -1
  5. pvlib/iotools/__init__.py +6 -0
  6. pvlib/iotools/psm3.py +1 -1
  7. pvlib/iotools/psm4.py +819 -0
  8. pvlib/iotools/pvgis.py +10 -2
  9. pvlib/iotools/tmy.py +3 -69
  10. pvlib/irradiance.py +38 -15
  11. pvlib/ivtools/sdm/__init__.py +20 -0
  12. pvlib/ivtools/sdm/_fit_desoto_pvsyst_sandia.py +585 -0
  13. pvlib/ivtools/sdm/cec.py +93 -0
  14. pvlib/ivtools/sdm/desoto.py +401 -0
  15. pvlib/ivtools/sdm/pvsyst.py +630 -0
  16. pvlib/location.py +73 -33
  17. pvlib/modelchain.py +19 -36
  18. pvlib/pvsystem.py +114 -65
  19. pvlib/snow.py +64 -28
  20. pvlib/spectrum/__init__.py +0 -1
  21. pvlib/spectrum/irradiance.py +2 -64
  22. pvlib/spectrum/mismatch.py +3 -3
  23. pvlib/tools.py +6 -5
  24. {pvlib-0.11.2.dist-info → pvlib-0.12.1a1.dist-info}/METADATA +6 -5
  25. pvlib-0.12.1a1.dist-info/RECORD +80 -0
  26. {pvlib-0.11.2.dist-info → pvlib-0.12.1a1.dist-info}/WHEEL +1 -1
  27. pvlib/data/BIRD_08_16_2012.csv +0 -8761
  28. pvlib/data/BIRD_08_16_2012_patm.csv +0 -8761
  29. pvlib/data/Burlington, United States SolarAnywhere Time Series 2021 Lat_44_465 Lon_-73_205 TMY3 format.csv +0 -8762
  30. pvlib/data/Burlington, United States SolarAnywhere Time Series 20210101 to 20210103 Lat_44_4675 Lon_-73_2075 SA format.csv +0 -578
  31. pvlib/data/Burlington, United States SolarAnywhere Typical GHI Year Lat_44_465 Lon_-73_205 SA format.csv +0 -74
  32. pvlib/data/CPS SCH275KTL-DO-US-800-250kW_275kVA_1.OND +0 -146
  33. pvlib/data/CRNS0101-05-2019-AZ_Tucson_11_W.txt +0 -4
  34. pvlib/data/CRN_with_problems.txt +0 -3
  35. pvlib/data/ET-M772BH550GL.PAN +0 -75
  36. pvlib/data/NLD_Amsterdam062400_IWEC.epw +0 -8768
  37. pvlib/data/PVsyst_demo.csv +0 -10757
  38. pvlib/data/PVsyst_demo_model.csv +0 -3588
  39. pvlib/data/SRML-day-EUPO1801.txt +0 -1441
  40. pvlib/data/abq19056.dat +0 -6
  41. pvlib/data/bishop88_numerical_precision.csv +0 -101
  42. pvlib/data/bsrn-lr0100-pay0616.dat +0 -86901
  43. pvlib/data/bsrn-pay0616.dat.gz +0 -0
  44. pvlib/data/cams_mcclear_1min_verbose.csv +0 -60
  45. pvlib/data/cams_mcclear_monthly.csv +0 -42
  46. pvlib/data/cams_radiation_1min_verbose.csv +0 -72
  47. pvlib/data/cams_radiation_monthly.csv +0 -47
  48. pvlib/data/detect_clearsky_data.csv +0 -35
  49. pvlib/data/detect_clearsky_threshold_data.csv +0 -126
  50. pvlib/data/greensboro_kimber_soil_manwash.dat +0 -8761
  51. pvlib/data/greensboro_kimber_soil_nowash.dat +0 -8761
  52. pvlib/data/inverter_fit_snl_meas.csv +0 -127
  53. pvlib/data/inverter_fit_snl_sim.csv +0 -19
  54. pvlib/data/ivtools_numdiff.csv +0 -52
  55. pvlib/data/midc_20181014.txt +0 -1441
  56. pvlib/data/midc_raw_20181018.txt +0 -1441
  57. pvlib/data/midc_raw_short_header_20191115.txt +0 -1441
  58. pvlib/data/msn19056.dat +0 -6
  59. pvlib/data/precise_iv_curves1.json +0 -10251
  60. pvlib/data/precise_iv_curves2.json +0 -10251
  61. pvlib/data/precise_iv_curves_parameter_sets1.csv +0 -33
  62. pvlib/data/precise_iv_curves_parameter_sets2.csv +0 -33
  63. pvlib/data/pvgis_hourly_Timeseries_45.000_8.000_SA2_10kWp_CIS_5_2a_2013_2014.json +0 -1
  64. pvlib/data/pvgis_hourly_Timeseries_45.000_8.000_SA_30deg_0deg_2016_2016.csv +0 -35
  65. pvlib/data/pvgis_tmy_meta.json +0 -32
  66. pvlib/data/pvgis_tmy_test.csv +0 -8761
  67. pvlib/data/pvwatts_8760_rackmount.csv +0 -8779
  68. pvlib/data/pvwatts_8760_roofmount.csv +0 -8779
  69. pvlib/data/singleaxis_tracker_wslope.csv +0 -8761
  70. pvlib/data/spectrl2_example_spectra.csv +0 -123
  71. pvlib/data/surfrad-slv16001.dat +0 -1442
  72. pvlib/data/test_psm3_2017.csv +0 -17521
  73. pvlib/data/test_psm3_2019_5min.csv +0 -289
  74. pvlib/data/test_psm3_tmy-2017.csv +0 -8761
  75. pvlib/data/test_read_psm3.csv +0 -17523
  76. pvlib/data/test_read_pvgis_horizon.csv +0 -49
  77. pvlib/data/tmy_45.000_8.000_2005_2023.csv +0 -8789
  78. pvlib/data/tmy_45.000_8.000_2005_2023.epw +0 -8768
  79. pvlib/data/tmy_45.000_8.000_2005_2023.json +0 -1
  80. pvlib/data/tmy_45.000_8.000_2005_2023.txt +0 -8761
  81. pvlib/data/tmy_45.000_8.000_userhorizon.json +0 -1
  82. pvlib/ivtools/sdm.py +0 -1379
  83. pvlib/spa_c_files/README.md +0 -81
  84. pvlib/spa_c_files/cspa_py.pxd +0 -43
  85. pvlib/spa_c_files/spa_py.pyx +0 -30
  86. pvlib/tests/__init__.py +0 -0
  87. pvlib/tests/bifacial/__init__.py +0 -0
  88. pvlib/tests/bifacial/test_infinite_sheds.py +0 -317
  89. pvlib/tests/bifacial/test_losses_models.py +0 -54
  90. pvlib/tests/bifacial/test_pvfactors.py +0 -82
  91. pvlib/tests/bifacial/test_utils.py +0 -192
  92. pvlib/tests/conftest.py +0 -476
  93. pvlib/tests/iotools/__init__.py +0 -0
  94. pvlib/tests/iotools/test_acis.py +0 -213
  95. pvlib/tests/iotools/test_bsrn.py +0 -131
  96. pvlib/tests/iotools/test_crn.py +0 -95
  97. pvlib/tests/iotools/test_epw.py +0 -23
  98. pvlib/tests/iotools/test_midc.py +0 -89
  99. pvlib/tests/iotools/test_panond.py +0 -32
  100. pvlib/tests/iotools/test_psm3.py +0 -198
  101. pvlib/tests/iotools/test_pvgis.py +0 -644
  102. pvlib/tests/iotools/test_sodapro.py +0 -298
  103. pvlib/tests/iotools/test_solaranywhere.py +0 -287
  104. pvlib/tests/iotools/test_solargis.py +0 -68
  105. pvlib/tests/iotools/test_solcast.py +0 -324
  106. pvlib/tests/iotools/test_solrad.py +0 -152
  107. pvlib/tests/iotools/test_srml.py +0 -124
  108. pvlib/tests/iotools/test_surfrad.py +0 -75
  109. pvlib/tests/iotools/test_tmy.py +0 -133
  110. pvlib/tests/ivtools/__init__.py +0 -0
  111. pvlib/tests/ivtools/test_sde.py +0 -230
  112. pvlib/tests/ivtools/test_sdm.py +0 -429
  113. pvlib/tests/ivtools/test_utils.py +0 -173
  114. pvlib/tests/spectrum/__init__.py +0 -0
  115. pvlib/tests/spectrum/conftest.py +0 -40
  116. pvlib/tests/spectrum/test_irradiance.py +0 -138
  117. pvlib/tests/spectrum/test_mismatch.py +0 -304
  118. pvlib/tests/spectrum/test_response.py +0 -124
  119. pvlib/tests/spectrum/test_spectrl2.py +0 -72
  120. pvlib/tests/test__deprecation.py +0 -97
  121. pvlib/tests/test_albedo.py +0 -84
  122. pvlib/tests/test_atmosphere.py +0 -351
  123. pvlib/tests/test_clearsky.py +0 -884
  124. pvlib/tests/test_conftest.py +0 -37
  125. pvlib/tests/test_iam.py +0 -555
  126. pvlib/tests/test_inverter.py +0 -213
  127. pvlib/tests/test_irradiance.py +0 -1487
  128. pvlib/tests/test_location.py +0 -356
  129. pvlib/tests/test_modelchain.py +0 -2020
  130. pvlib/tests/test_numerical_precision.py +0 -124
  131. pvlib/tests/test_pvarray.py +0 -71
  132. pvlib/tests/test_pvsystem.py +0 -2511
  133. pvlib/tests/test_scaling.py +0 -207
  134. pvlib/tests/test_shading.py +0 -391
  135. pvlib/tests/test_singlediode.py +0 -608
  136. pvlib/tests/test_snow.py +0 -212
  137. pvlib/tests/test_soiling.py +0 -230
  138. pvlib/tests/test_solarposition.py +0 -966
  139. pvlib/tests/test_spa.py +0 -454
  140. pvlib/tests/test_temperature.py +0 -470
  141. pvlib/tests/test_tools.py +0 -146
  142. pvlib/tests/test_tracking.py +0 -474
  143. pvlib/tests/test_transformer.py +0 -60
  144. pvlib-0.11.2.dist-info/RECORD +0 -191
  145. {pvlib-0.11.2.dist-info → pvlib-0.12.1a1.dist-info/licenses}/AUTHORS.md +0 -0
  146. {pvlib-0.11.2.dist-info → pvlib-0.12.1a1.dist-info/licenses}/LICENSE +0 -0
  147. {pvlib-0.11.2.dist-info → pvlib-0.12.1a1.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)