Skip to content

Commit aeed5fa

Browse files
committed
add numba-capable nrel_earthsun_distance
1 parent 4fe05d4 commit aeed5fa

File tree

4 files changed

+126
-9
lines changed

4 files changed

+126
-9
lines changed

pvlib/solarposition.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -771,3 +771,49 @@ def pyephem_earthsun_distance(time):
771771
earthsun.append(sun.earth_distance)
772772

773773
return pd.Series(earthsun, index=time)
774+
775+
776+
def nrel_earthsun_distance(time, how='numpy', delta_t=None, numthreads=4):
777+
"""
778+
Calculates the distance from the earth to the sun using pyephem.
779+
780+
Parameters
781+
----------
782+
time : pd.DatetimeIndex
783+
784+
how : str, optional
785+
Options are 'numpy' or 'numba'. If numba >= 0.17.0
786+
is installed, how='numba' will compile the spa functions
787+
to machine code and run them multithreaded.
788+
789+
delta_t : float, optional
790+
Difference between terrestrial time and UT1.
791+
By default, use USNO historical data and predictions
792+
793+
numthreads : int, optional
794+
Number of threads to use if how == 'numba'.
795+
796+
Returns
797+
-------
798+
R : pd.Series
799+
Earth-sun distance in AU.
800+
"""
801+
delta_t = delta_t or 67.0
802+
803+
if not isinstance(time, pd.DatetimeIndex):
804+
try:
805+
time = pd.DatetimeIndex(time)
806+
except (TypeError, ValueError):
807+
time = pd.DatetimeIndex([time, ])
808+
809+
# must convert to midnight UTC on day of interest
810+
utcday = pd.DatetimeIndex(time.date).tz_localize('UTC')
811+
unixtime = utcday.astype(np.int64)/10**9
812+
813+
spa = _spa_python_import(how)
814+
815+
R = spa.earthsun_distance(unixtime, delta_t, numthreads)
816+
817+
R = pd.Series(R, index=time)
818+
819+
return R

pvlib/spa.py

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -907,6 +907,7 @@ def solar_position_loop(unixtime, loc_args, out):
907907
delta_t = loc_args[5]
908908
atmos_refract = loc_args[6]
909909
sst = loc_args[7]
910+
esd = loc_args[8]
910911

911912
for i in range(unixtime.shape[0]):
912913
utime = unixtime[i]
@@ -918,6 +919,9 @@ def solar_position_loop(unixtime, loc_args, out):
918919
L = heliocentric_longitude(jme)
919920
B = heliocentric_latitude(jme)
920921
R = heliocentric_radius_vector(jme)
922+
if esd:
923+
out[0, i] = R
924+
continue
921925
Theta = geocentric_longitude(L)
922926
beta = geocentric_latitude(B)
923927
x0 = mean_elongation(jce)
@@ -970,14 +974,24 @@ def solar_position_loop(unixtime, loc_args, out):
970974

971975

972976
def solar_position_numba(unixtime, lat, lon, elev, pressure, temp, delta_t,
973-
atmos_refract, numthreads, sst=False):
977+
atmos_refract, numthreads, sst=False, esd=False):
974978
"""Calculate the solar position using the numba compiled functions
975979
and multiple threads. Very slow if functions are not numba compiled.
976980
"""
981+
# these args are the same for each thread
977982
loc_args = np.array([lat, lon, elev, pressure, temp, delta_t,
978-
atmos_refract, sst])
983+
atmos_refract, sst, esd])
984+
985+
# construct dims x ulength array to put the results in
979986
ulength = unixtime.shape[0]
980-
result = np.empty((6, ulength), dtype=np.float64)
987+
if sst:
988+
dims = 3
989+
elif esd:
990+
dims = 1
991+
else:
992+
dims = 6
993+
result = np.empty((dims, ulength), dtype=np.float64)
994+
981995
if unixtime.dtype != np.float64:
982996
unixtime = unixtime.astype(np.float64)
983997

@@ -992,6 +1006,7 @@ def solar_position_numba(unixtime, lat, lon, elev, pressure, temp, delta_t,
9921006
solar_position_loop(unixtime, loc_args, result)
9931007
return result
9941008

1009+
# split the input and output arrays into numthreads chunks
9951010
split0 = np.array_split(unixtime, numthreads)
9961011
split2 = np.array_split(result, numthreads, axis=1)
9971012
chunks = [[a0, loc_args, split2[i]] for i, a0 in enumerate(split0)]
@@ -1006,7 +1021,7 @@ def solar_position_numba(unixtime, lat, lon, elev, pressure, temp, delta_t,
10061021

10071022

10081023
def solar_position_numpy(unixtime, lat, lon, elev, pressure, temp, delta_t,
1009-
atmos_refract, numthreads, sst=False):
1024+
atmos_refract, numthreads, sst=False, esd=False):
10101025
"""Calculate the solar position assuming unixtime is a numpy array. Note
10111026
this function will not work if the solar position functions were
10121027
compiled with numba.
@@ -1020,6 +1035,8 @@ def solar_position_numpy(unixtime, lat, lon, elev, pressure, temp, delta_t,
10201035
L = heliocentric_longitude(jme)
10211036
B = heliocentric_latitude(jme)
10221037
R = heliocentric_radius_vector(jme)
1038+
if esd:
1039+
return (R, )
10231040
Theta = geocentric_longitude(L)
10241041
beta = geocentric_latitude(B)
10251042
x0 = mean_elongation(jce)
@@ -1063,7 +1080,7 @@ def solar_position_numpy(unixtime, lat, lon, elev, pressure, temp, delta_t,
10631080

10641081

10651082
def solar_position(unixtime, lat, lon, elev, pressure, temp, delta_t,
1066-
atmos_refract, numthreads=8, sst=False):
1083+
atmos_refract, numthreads=8, sst=False, esd=False):
10671084

10681085
"""
10691086
Calculate the solar position using the
@@ -1100,6 +1117,11 @@ def solar_position(unixtime, lat, lon, elev, pressure, temp, delta_t,
11001117
numthreads: int, optional
11011118
Number of threads to use for computation if numba>=0.17
11021119
is installed.
1120+
sst : bool
1121+
If True, return only data needed for sunrise, sunset, and transit
1122+
calculations.
1123+
esd : bool
1124+
If True, return only Earth-Sun distance in AU
11031125
11041126
Returns
11051127
-------
@@ -1126,7 +1148,7 @@ def solar_position(unixtime, lat, lon, elev, pressure, temp, delta_t,
11261148

11271149
result = do_calc(unixtime, lat, lon, elev, pressure,
11281150
temp, delta_t, atmos_refract, numthreads,
1129-
sst)
1151+
sst, esd)
11301152

11311153
if not isinstance(result, np.ndarray):
11321154
try:
@@ -1241,3 +1263,31 @@ def transit_sunrise_sunset(dates, lat, lon, delta_t, numthreads):
12411263
sunset = S + utday
12421264

12431265
return transit, sunrise, sunset
1266+
1267+
1268+
def earthsun_distance(unixtime, delta_t, numthreads):
1269+
"""
1270+
Calculate the sun transit, sunrise, and sunset
1271+
for a set of dates at a given location.
1272+
1273+
Parameters
1274+
----------
1275+
unixtime : numpy array
1276+
Array of unix/epoch timestamps to calculate solar position for.
1277+
Unixtime is the number of seconds since Jan. 1, 1970 00:00:00 UTC.
1278+
A pandas.DatetimeIndex is easily converted using .astype(np.int64)/10**9
1279+
delta_t : float
1280+
Difference between terrestrial time and UT. USNO has tables.
1281+
numthreads : int
1282+
Number to threads to use for calculation (if using numba)
1283+
1284+
Returns
1285+
-------
1286+
R : array
1287+
Earth-Sun distance in AU.
1288+
"""
1289+
1290+
R = solar_position(unixtime, 0, 0, 0, 0, 0, delta_t,
1291+
0, numthreads, esd=True)[0]
1292+
1293+
return R

pvlib/test/test_solarposition.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
import numpy as np
44
import pandas as pd
55

6-
from pandas.util.testing import assert_frame_equal, assert_index_equal
6+
from pandas.util.testing import (assert_frame_equal, assert_series_equal,
7+
assert_index_equal)
78
from numpy.testing import assert_allclose
89
import pytest
910

@@ -320,3 +321,19 @@ def test_get_solarposition_no_kwargs(expected_solpos):
320321
expected_solpos = np.round(expected_solpos, 2)
321322
ephem_data = np.round(ephem_data, 2)
322323
assert_frame_equal(expected_solpos, ephem_data[expected_solpos.columns])
324+
325+
326+
def test_nrel_earthsun_distance():
327+
times = pd.DatetimeIndex([datetime.datetime(2015, 1, 2),
328+
datetime.datetime(2015, 8, 2),]
329+
).tz_localize('MST')
330+
result = solarposition.nrel_earthsun_distance(times, delta_t=64.0)
331+
expected = pd.Series(np.array([0.983293144266, 1.01489789116]),
332+
index=times)
333+
assert_series_equal(expected, result)
334+
335+
times = datetime.datetime(2015, 1, 2)
336+
result = solarposition.nrel_earthsun_distance(times, delta_t=64.0)
337+
expected = pd.Series(np.array([0.983293144266]),
338+
index=pd.DatetimeIndex([times, ]))
339+
assert_series_equal(expected, result)

pvlib/test/test_spa.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,6 @@ def test_equation_of_time(self):
240240
assert_almost_equal(eot, self.spa.equation_of_time(
241241
M, alpha, dPsi, epsilon), 2)
242242

243-
244243
def test_transit_sunrise_sunset(self):
245244
# tests at greenwich
246245
times = pd.DatetimeIndex([dt.datetime(1996, 7, 5, 0),
@@ -310,7 +309,12 @@ def test_transit_sunrise_sunset(self):
310309
assert_almost_equal(sunrise/1e3, result[1]/1e3, 1)
311310
assert_almost_equal(sunset/1e3, result[2]/1e3, 1)
312311

313-
312+
def test_earthsun_distance(self):
313+
times = (pd.date_range('2003-10-17 12:30:30', periods=1, freq='D')
314+
.tz_localize('MST'))
315+
unixtimes = times.tz_convert('UTC').astype(np.int64)*1.0/10**9
316+
result = self.spa.earthsun_distance(unixtimes, 64.0, 1)
317+
assert_almost_equal(R, result, 6)
314318

315319
class NumpySpaTest(unittest.TestCase, SpaBase):
316320
"""Import spa without compiling to numba then run tests"""

0 commit comments

Comments
 (0)