Skip to content

Commit 0b918f1

Browse files
VolkrBwholmgren
authored andcommitted
Issue 165: Added calculate_deltaT function into spa.py (#240)
* Added calculate_deltaT function into spa.py based on the equations given by http://eclipse.gsfc.nasa.gov/SEcat5/deltatpoly.html * Replaced elsif with np.where, brought down to 1 function * removed a space * typo * another typo * added recursive deltat for clarity * Added delta_t,format,1test * typo * added deltat solarposition test * added atol=1e-4 to several tests * Added some docstrings to spa module. Added xfail. * Set delta_t=67 in solarposition.py, docstrings * rm/add xfail, added test fixture in solarpos, typo * Moved xfail into parametrize decorator
1 parent 23a4d1a commit 0b918f1

File tree

6 files changed

+228
-16
lines changed

6 files changed

+228
-16
lines changed

docs/sphinx/source/whatsnew/v0.4.2.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,17 @@ API Changes
1616
~~~~~~~~~~~
1717

1818
* The run_model method of the ModelChain will use the weather parameter of all weather data instead of splitting it to irradiation and weather. The irradiation parameter still works but will be removed soon.(:issue:`239`)
19+
* delta_t kwarg is now 67.0 instead of None. IMPORTANT: Setting delta_t as None will break the code for Numba calculation. This will be fixed in a future version. (:issue:`165`)
1920

2021

2122
Enhancements
2223
~~~~~~~~~~~~
2324

2425
* Adding a complete_irradiance method to the ModelChain to make it possible to calculate missing irradiation data from the existing columns [beta] (:issue:`239`)
25-
26+
* Added calculate_deltat method to the spa module to calculate the time difference between terrestrial time and UT1. Specifying a scalar is sufficient for most calculations. (:issue:`165`)
2627

2728
Code Contributors
2829
~~~~~~~~~~~~~~~~~
2930

3031
* Uwe Krien
32+
* Volker Beutner

pvlib/solarposition.py

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ def _spa_python_import(how):
238238

239239

240240
def spa_python(time, latitude, longitude,
241-
altitude=0, pressure=101325, temperature=12, delta_t=None,
241+
altitude=0, pressure=101325, temperature=12, delta_t=67.0,
242242
atmos_refract=None, how='numpy', numthreads=4, **kwargs):
243243
"""
244244
Calculate the solar position using a python implementation of the
@@ -261,7 +261,12 @@ def spa_python(time, latitude, longitude,
261261
temperature : int or float, optional
262262
avg. yearly air temperature in degrees C.
263263
delta_t : float, optional
264+
If delta_t is None, uses spa.calculate_deltat
265+
using time.year and time.month from pandas.DatetimeIndex.
266+
For most simulations specifing delta_t is sufficient.
264267
Difference between terrestrial time and UT1.
268+
*Note: delta_t = None will break code using nrel_numba,
269+
this will be fixed in a future version.
265270
The USNO has historical and forecasted delta_t [3].
266271
atmos_refrac : float, optional
267272
The approximate atmospheric refraction (in degrees)
@@ -308,7 +313,7 @@ def spa_python(time, latitude, longitude,
308313
lon = longitude
309314
elev = altitude
310315
pressure = pressure / 100 # pressure must be in millibars for calculation
311-
delta_t = delta_t or 67.0
316+
312317
atmos_refract = atmos_refract or 0.5667
313318

314319
if not isinstance(time, pd.DatetimeIndex):
@@ -321,6 +326,8 @@ def spa_python(time, latitude, longitude,
321326

322327
spa = _spa_python_import(how)
323328

329+
delta_t = delta_t or spa.calculate_deltat(time.year, time.month)
330+
324331
app_zenith, zenith, app_elevation, elevation, azimuth, eot = spa.solar_position(
325332
unixtime, lat, lon, elev, pressure, temperature, delta_t,
326333
atmos_refract, numthreads)
@@ -335,7 +342,7 @@ def spa_python(time, latitude, longitude,
335342

336343

337344
def get_sun_rise_set_transit(time, latitude, longitude, how='numpy',
338-
delta_t=None,
345+
delta_t=67.0,
339346
numthreads=4):
340347
"""
341348
Calculate the sunrise, sunset, and sun transit times using the
@@ -353,7 +360,12 @@ def get_sun_rise_set_transit(time, latitude, longitude, how='numpy',
353360
latitude : float
354361
longitude : float
355362
delta_t : float, optional
363+
If delta_t is None, uses spa.calculate_deltat
364+
using time.year and time.month from pandas.DatetimeIndex.
365+
For most simulations specifing delta_t is sufficient.
356366
Difference between terrestrial time and UT1.
367+
*Note: delta_t = None will break code using nrel_numba,
368+
this will be fixed in a future version.
357369
By default, use USNO historical data and predictions
358370
how : str, optional
359371
Options are 'numpy' or 'numba'. If numba >= 0.17.0
@@ -380,7 +392,6 @@ def get_sun_rise_set_transit(time, latitude, longitude, how='numpy',
380392

381393
lat = latitude
382394
lon = longitude
383-
delta_t = delta_t or 67.0
384395

385396
if not isinstance(time, pd.DatetimeIndex):
386397
try:
@@ -394,6 +405,8 @@ def get_sun_rise_set_transit(time, latitude, longitude, how='numpy',
394405

395406
spa = _spa_python_import(how)
396407

408+
delta_t = delta_t or spa.calculate_deltat(time.year, time.month)
409+
397410
transit, sunrise, sunset = spa.transit_sunrise_sunset(
398411
unixtime, lat, lon, delta_t, numthreads)
399412

@@ -773,7 +786,7 @@ def pyephem_earthsun_distance(time):
773786
return pd.Series(earthsun, index=time)
774787

775788

776-
def nrel_earthsun_distance(time, how='numpy', delta_t=None, numthreads=4):
789+
def nrel_earthsun_distance(time, how='numpy', delta_t=67.0, numthreads=4):
777790
"""
778791
Calculates the distance from the earth to the sun using the
779792
NREL SPA algorithm described in [1].
@@ -788,7 +801,12 @@ def nrel_earthsun_distance(time, how='numpy', delta_t=None, numthreads=4):
788801
to machine code and run them multithreaded.
789802
790803
delta_t : float, optional
804+
If delta_t is None, uses spa.calculate_deltat
805+
using time.year and time.month from pandas.DatetimeIndex.
806+
For most simulations specifing delta_t is sufficient.
791807
Difference between terrestrial time and UT1.
808+
*Note: delta_t = None will break code using nrel_numba,
809+
this will be fixed in a future version.
792810
By default, use USNO historical data and predictions
793811
794812
numthreads : int, optional
@@ -805,7 +823,6 @@ def nrel_earthsun_distance(time, how='numpy', delta_t=None, numthreads=4):
805823
radiation applications. Technical report: NREL/TP-560- 34302. Golden,
806824
USA, http://www.nrel.gov.
807825
"""
808-
delta_t = delta_t or 67.0
809826

810827
if not isinstance(time, pd.DatetimeIndex):
811828
try:
@@ -817,6 +834,8 @@ def nrel_earthsun_distance(time, how='numpy', delta_t=None, numthreads=4):
817834

818835
spa = _spa_python_import(how)
819836

837+
delta_t = delta_t or spa.calculate_deltat(time.year, time.month)
838+
820839
R = spa.earthsun_distance(unixtime, delta_t, numthreads)
821840

822841
R = pd.Series(R, index=time)

pvlib/spa.py

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1109,7 +1109,12 @@ def solar_position(unixtime, lat, lon, elev, pressure, temp, delta_t,
11091109
avg. yearly temperature at location in
11101110
degrees C; used for atmospheric correction
11111111
delta_t : float, optional
1112+
If delta_t is None, uses spa.calculate_deltat
1113+
using time.year and time.month from pandas.DatetimeIndex.
1114+
For most simulations specifing delta_t is sufficient.
11121115
Difference between terrestrial time and UT1.
1116+
*Note: delta_t = None will break code using nrel_numba,
1117+
this will be fixed in a future version.
11131118
By default, use USNO historical data and predictions
11141119
atmos_refrac : float, optional
11151120
The approximate atmospheric refraction (in degrees)
@@ -1297,3 +1302,134 @@ def earthsun_distance(unixtime, delta_t, numthreads):
12971302
0, numthreads, esd=True)[0]
12981303

12991304
return R
1305+
1306+
1307+
def calculate_deltat(year, month):
1308+
"""Calculate the difference between Terrestrial Dynamical Time (TD)
1309+
and Universal Time (UT).
1310+
1311+
Note: This function is not yet compatible for calculations using
1312+
Numba.
1313+
1314+
Equations taken from http://eclipse.gsfc.nasa.gov/SEcat5/deltatpoly.html
1315+
"""
1316+
1317+
plw = ' Deltat is unknown for years before -1999 and after 3000.'\
1318+
+ ' Delta values will be calculated, but the calculations'\
1319+
+ ' are not intended to be used for these years.'
1320+
1321+
try:
1322+
if np.any((year > 3000) | (year < -1999)):
1323+
pvl_logger.warning(plw)
1324+
except ValueError:
1325+
if (year > 3000) | (year < -1999):
1326+
pvl_logger.warning(plw)
1327+
except TypeError:
1328+
return 0
1329+
1330+
y = year + (month - 0.5)/12
1331+
1332+
deltat = np.where(year < -500,
1333+
1334+
-20+32*((y-1820)/100)**2, 0)
1335+
1336+
deltat = np.where((-500 <= year) & (year < 500),
1337+
1338+
10583.6-1014.41*(y/100)
1339+
+ 33.78311*(y/100)**2
1340+
- 5.952053*(y/100)**3
1341+
- 0.1798452*(y/100)**4
1342+
+ 0.022174192*(y/100)**5
1343+
+ 0.0090316521*(y/100)**6, deltat)
1344+
1345+
deltat = np.where((500 <= year) & (year < 1600),
1346+
1347+
1574.2-556.01*((y-1000)/100)
1348+
+ 71.23472*((y-1000)/100)**2
1349+
+ 0.319781*((y-1000)/100)**3
1350+
- 0.8503463*((y-1000)/100)**4
1351+
- 0.005050998*((y-1000)/100)**5
1352+
+ 0.0083572073*((y-1000)/100)**6, deltat)
1353+
1354+
deltat = np.where((1600 <= year) & (year < 1700),
1355+
1356+
120-0.9808*(y-1600)
1357+
- 0.01532*(y-1600)**2
1358+
+ (y-1600)**3/7129, deltat)
1359+
1360+
deltat = np.where((1700 <= year) & (year < 1800),
1361+
1362+
8.83+0.1603*(y-1700)
1363+
- 0.0059285*(y-1700)**2
1364+
+ 0.00013336*(y-1700)**3
1365+
- (y-1700)**4/1174000, deltat)
1366+
1367+
deltat = np.where((1800 <= year) & (year < 1860),
1368+
1369+
13.72-0.332447*(y-1800)
1370+
+ 0.0068612*(y-1800)**2
1371+
+ 0.0041116*(y-1800)**3
1372+
- 0.00037436*(y-1800)**4
1373+
+ 0.0000121272*(y-1800)**5
1374+
- 0.0000001699*(y-1800)**6
1375+
+ 0.000000000875*(y-1800)**7, deltat)
1376+
1377+
deltat = np.where((1860 <= year) & (year < 1900),
1378+
1379+
7.6+0.5737*(y-1860)
1380+
- 0.251754*(y-1860)**2
1381+
+ 0.01680668*(y-1860)**3
1382+
- 0.0004473624*(y-1860)**4
1383+
+ (y-1860)**5/233174, deltat)
1384+
1385+
deltat = np.where((1900 <= year) & (year < 1920),
1386+
1387+
-2.79+1.494119*(y-1900)
1388+
- 0.0598939*(y-1900)**2
1389+
+ 0.0061966*(y-1900)**3
1390+
- 0.000197*(y-1900)**4, deltat)
1391+
1392+
deltat = np.where((1920 <= year) & (year < 1941),
1393+
1394+
21.20+0.84493*(y-1920)
1395+
- 0.076100*(y-1920)**2
1396+
+ 0.0020936*(y-1920)**3, deltat)
1397+
1398+
deltat = np.where((1941 <= year) & (year < 1961),
1399+
1400+
29.07+0.407*(y-1950)
1401+
- (y-1950)**2/233
1402+
+ (y-1950)**3/2547, deltat)
1403+
1404+
deltat = np.where((1961 <= year) & (year < 1986),
1405+
1406+
45.45+1.067*(y-1975)
1407+
- (y-1975)**2/260
1408+
- (y-1975)**3/718, deltat)
1409+
1410+
deltat = np.where((1986 <= year) & (year < 2005),
1411+
1412+
63.86+0.3345*(y-2000)
1413+
- 0.060374*(y-2000)**2
1414+
+ 0.0017275*(y-2000)**3
1415+
+ 0.000651814*(y-2000)**4
1416+
+ 0.00002373599*(y-2000)**5, deltat)
1417+
1418+
deltat = np.where((2005 <= year) & (year < 2050),
1419+
1420+
62.92+0.32217*(y-2000)
1421+
+ 0.005589*(y-2000)**2, deltat)
1422+
1423+
deltat = np.where((2050 <= year) & (year < 2150),
1424+
1425+
-20+32*((y-1820)/100)**2
1426+
- 0.5628*(2150-y), deltat)
1427+
1428+
deltat = np.where(year > 2150,
1429+
1430+
-20+32*((y-1820)/100)**2, deltat)
1431+
1432+
deltat = np.asscalar(deltat) if np.isscalar(year) & np.isscalar(month)\
1433+
else deltat
1434+
1435+
return deltat

pvlib/test/test_irradiance.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ def test_grounddiffuse_albedo_invalid_surface():
9898

9999
def test_grounddiffuse_albedo_surface():
100100
result = irradiance.grounddiffuse(40, ghi, surface_type='sand')
101-
assert_allclose(result, [0, 3.731058, 48.778813, 12.035025])
101+
assert_allclose(result, [0, 3.731058, 48.778813, 12.035025], atol=1e-4)
102102

103103

104104
def test_isotropic_float():
@@ -108,7 +108,7 @@ def test_isotropic_float():
108108

109109
def test_isotropic_series():
110110
result = irradiance.isotropic(40, irrad_data['dhi'])
111-
assert_allclose(result, [0, 35.728402, 104.601328, 54.777191])
111+
assert_allclose(result, [0, 35.728402, 104.601328, 54.777191], atol=1e-4)
112112

113113

114114
def test_klucher_series_float():
@@ -120,29 +120,29 @@ def test_klucher_series():
120120
result = irradiance.klucher(40, 180, irrad_data['dhi'], irrad_data['ghi'],
121121
ephem_data['apparent_zenith'],
122122
ephem_data['azimuth'])
123-
assert_allclose(result, [0, 37.446276, 109.209347, 56.965916])
123+
assert_allclose(result, [0, 37.446276, 109.209347, 56.965916], atol=1e-4)
124124

125125

126126
def test_haydavies():
127127
result = irradiance.haydavies(40, 180, irrad_data['dhi'], irrad_data['dni'],
128128
dni_et,
129129
ephem_data['apparent_zenith'],
130130
ephem_data['azimuth'])
131-
assert_allclose(result, [0, 14.967008, 102.994862, 33.190865])
131+
assert_allclose(result, [0, 14.967008, 102.994862, 33.190865], atol=1e-4)
132132

133133

134134
def test_reindl():
135135
result = irradiance.reindl(40, 180, irrad_data['dhi'], irrad_data['dni'],
136136
irrad_data['ghi'], dni_et,
137137
ephem_data['apparent_zenith'],
138138
ephem_data['azimuth'])
139-
assert_allclose(result, [np.nan, 15.730664, 104.131724, 34.166258])
139+
assert_allclose(result, [np.nan, 15.730664, 104.131724, 34.166258], atol=1e-4)
140140

141141

142142
def test_king():
143143
result = irradiance.king(40, irrad_data['dhi'], irrad_data['ghi'],
144144
ephem_data['apparent_zenith'])
145-
assert_allclose(result, [0, 44.629352, 115.182626, 79.719855])
145+
assert_allclose(result, [0, 44.629352, 115.182626, 79.719855], atol=1e-4)
146146

147147

148148
def test_perez():

pvlib/test/test_solarposition.py

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,14 @@ def expected_solpos():
3636
'apparent_elevation': 39.888378},
3737
index=['2003-10-17T12:30:30Z'])
3838

39+
@pytest.fixture()
40+
def expected_solpos_multi():
41+
return pd.DataFrame({'elevation': [39.872046, 39.505196],
42+
'apparent_zenith': [50.111622, 50.478260],
43+
'azimuth': [194.340241, 194.311132],
44+
'apparent_elevation': [39.888378, 39.521740]},
45+
index=[['2003-10-17T12:30:30Z', '2003-10-18T12:30:30Z']])
46+
3947
# the physical tests are run at the same time as the NREL SPA test.
4048
# pyephem reproduces the NREL result to 2 decimal places.
4149
# this doesn't mean that one code is better than the other.
@@ -63,7 +71,6 @@ def test_spa_c_physical_dst(expected_solpos):
6371
expected_solpos.index = times
6472
assert_frame_equal(expected_solpos, ephem_data[expected_solpos.columns])
6573

66-
6774
def test_spa_python_numpy_physical(expected_solpos):
6875
times = pd.date_range(datetime.datetime(2003,10,17,12,30,30),
6976
periods=1, freq='D', tz=golden_mst.tz)
@@ -76,7 +83,6 @@ def test_spa_python_numpy_physical(expected_solpos):
7683
expected_solpos.index = times
7784
assert_frame_equal(expected_solpos, ephem_data[expected_solpos.columns])
7885

79-
8086
def test_spa_python_numpy_physical_dst(expected_solpos):
8187
times = pd.date_range(datetime.datetime(2003,10,17,13,30,30),
8288
periods=1, freq='D', tz=golden.tz)
@@ -312,6 +318,30 @@ def test_get_solarposition_altitude(altitude, expected):
312318
assert_frame_equal(this_expected, ephem_data[this_expected.columns])
313319

314320

321+
@pytest.mark.parametrize(
322+
"delta_t, method, expected", [
323+
(None, 'nrel_numpy', expected_solpos_multi()),
324+
(67.0, 'nrel_numpy', expected_solpos_multi()),
325+
pytest.mark.xfail(raises=ValueError, reason = 'spa.calculate_deltat not implemented for numba yet')
326+
((None, 'nrel_numba', expected_solpos_multi())),
327+
(67.0, 'nrel_numba', expected_solpos_multi())
328+
])
329+
def test_get_solarposition_deltat(delta_t, method, expected):
330+
times = pd.date_range(datetime.datetime(2003,10,17,13,30,30),
331+
periods=2, freq='D', tz=golden.tz)
332+
ephem_data = solarposition.get_solarposition(times, golden.latitude,
333+
golden.longitude,
334+
pressure=82000,
335+
delta_t=delta_t,
336+
temperature=11,
337+
method=method)
338+
this_expected = expected.copy()
339+
this_expected.index = times
340+
this_expected = np.round(this_expected, 5)
341+
ephem_data = np.round(ephem_data, 5)
342+
assert_frame_equal(this_expected, ephem_data[this_expected.columns])
343+
344+
315345
def test_get_solarposition_no_kwargs(expected_solpos):
316346
times = pd.date_range(datetime.datetime(2003,10,17,13,30,30),
317347
periods=1, freq='D', tz=golden.tz)

0 commit comments

Comments
 (0)