From 90ea2e0c57e17ce44a00781c1f33e0680a928fda Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Fri, 21 Sep 2018 15:02:52 -0600 Subject: [PATCH 01/33] Initial commit for ephem_next_rise_set --- pvlib/solarposition.py | 79 +++++++++++++++++++++++++++++++- pvlib/test/test_solarposition.py | 61 ++++++++++++++++-------- 2 files changed, 121 insertions(+), 19 deletions(-) diff --git a/pvlib/solarposition.py b/pvlib/solarposition.py index 7f3ed3f8fb..86a2fff5c3 100644 --- a/pvlib/solarposition.py +++ b/pvlib/solarposition.py @@ -438,6 +438,24 @@ def get_sun_rise_set_transit(time, latitude, longitude, how='numpy', return result +def _ephem_convert_to_seconds_and_microseconds(date): + # utility from unreleased PyEphem 3.6.7.1 + """Converts a PyEphem date into seconds""" + microseconds = int(round(24 * 60 * 60 * 1000000 * date)) + seconds, microseconds = divmod(microseconds, 1000000) + seconds -= 2209032000 # difference between epoch 1900 and epoch 1970 + return seconds, microseconds + + +def _ephem_to_timezone(date, tzinfo): + # utility from unreleased PyEphem 3.6.7.1 + """"Convert a PyEphem date into a timezone aware Python datetime representation.""" + seconds, microseconds = _ephem_convert_to_seconds_and_microseconds(date) + date = dt.datetime.fromtimestamp(seconds, tzinfo) + date = date.replace(microsecond=microseconds) + return date + + def _ephem_setup(latitude, longitude, altitude, pressure, temperature): import ephem # initialize a PyEphem observer @@ -453,6 +471,63 @@ def _ephem_setup(latitude, longitude, altitude, pressure, temperature): return obs, sun +def ephem_next_rise_set(time, latitude, longitude, altitude=0, + pressure=101325, temperature=12): + """ + Calculate the next sunrise and sunset times using the PyEphem package. + + Parameters + ---------- + time : pandas.DatetimeIndex + Localized or UTC. + latitude : float + positive is north of 0 + longitude : float + positive is east of 0 + altitude : float, default 0 + distance above sea level in meters. + pressure : int or float, optional, default 101325 + air pressure in Pascals. + temperature : int or float, optional, default 12 + air temperature in degrees C. + + Returns + ------- + pandas.DataFrame + index is the same as input time.index + columns are 'sunrise' and 'sunset', times are localized to the + timezone time.tz + + See also + -------- + pyephem + """ + + try: + import ephem + except ImportError: + raise ImportError('PyEphem must be installed') + + # if localized, convert to UTC. otherwise, assume UTC. + try: + time_utc = time.tz_convert('UTC') + except TypeError: + time_utc = time + + obs, sun = _ephem_setup(latitude, longitude, altitude, + pressure, temperature) + # create lists of the next sunrise and sunset time localized to time.tz + next_sunrise = [] + next_sunset = [] + for thetime in time_utc: + obs.date = ephem.Date(thetime) + next_sunrise.append(_ephem_to_timezone(obs.next_rising(sun), time.tz)) + next_sunset.append(_ephem_to_timezone(obs.next_setting(sun), time.tz)) + + return pd.DataFrame(index=time, data={'sunrise' : next_sunrise, + 'sunset' : next_sunset}) + + def pyephem(time, latitude, longitude, altitude=0, pressure=101325, temperature=12): """ @@ -463,9 +538,11 @@ def pyephem(time, latitude, longitude, altitude=0, pressure=101325, time : pandas.DatetimeIndex Localized or UTC. latitude : float + positive is north of 0 longitude : float + positive is east of 0 altitude : float, default 0 - distance above sea level. + distance above sea level in meters. pressure : int or float, optional, default 101325 air pressure in Pascals. temperature : int or float, optional, default 12 diff --git a/pvlib/test/test_solarposition.py b/pvlib/test/test_solarposition.py index b99e5e7ff4..2ed387be9f 100644 --- a/pvlib/test/test_solarposition.py +++ b/pvlib/test/test_solarposition.py @@ -45,6 +45,21 @@ def expected_solpos_multi(): 'apparent_elevation': [39.888378, 39.521740]}, index=[['2003-10-17T12:30:30Z', '2003-10-18T12:30:30Z']]) +@pytest.fixture() +def expected_rise_set(): + # for Golden, CO, from USNO website + times = pd.DatetimeIndex([datetime.datetime(2015, 1, 2), + datetime.datetime(2015, 8, 2),] + ).tz_localize('MST') + sunrise = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 7, 19, 2), + datetime.datetime(2015, 8, 2, 5, 1, 26) + ]).tz_localize('MST').tolist() + sunset = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 16, 49, 10), + datetime.datetime(2015, 8, 2, 19, 11, 31) + ]).tz_localize('MST').tolist() + return pd.DataFrame({'sunrise':sunrise, 'sunset':sunset}, index=times) + + # the physical tests are run at the same time as the NREL SPA test. # pyephem reproduces the NREL result to 2 decimal places. # this doesn't mean that one code is better than the other. @@ -125,7 +140,7 @@ def test_spa_python_numba_physical_dst(expected_solpos): @needs_pandas_0_17 -def test_get_sun_rise_set_transit(): +def test_get_sun_rise_set_transit(expected_rise_set): south = Location(-35.0, 0.0, tz='UTC') times = pd.DatetimeIndex([datetime.datetime(1996, 7, 5, 0), datetime.datetime(2004, 12, 4, 0)] @@ -136,10 +151,11 @@ def test_get_sun_rise_set_transit(): sunset = pd.DatetimeIndex([datetime.datetime(1996, 7, 5, 17, 1, 4), datetime.datetime(2004, 12, 4, 19, 2, 2)] ).tz_localize('UTC').tolist() + frame = pd.DataFrame({'sunrise':sunrise, 'sunset':sunset}, index=times) + result = solarposition.get_sun_rise_set_transit(times, south.latitude, south.longitude, delta_t=64.0) - frame = pd.DataFrame({'sunrise':sunrise, 'sunset':sunset}, index=times) result_rounded = pd.DataFrame(index=result.index) # need to iterate because to_datetime does not accept 2D data # the rounding fails on pandas < 0.17 @@ -150,23 +166,11 @@ def test_get_sun_rise_set_transit(): del result_rounded['transit'] assert_frame_equal(frame, result_rounded) - - # tests from USNO - # Golden - golden = Location(39.0, -105.0, tz='MST') - times = pd.DatetimeIndex([datetime.datetime(2015, 1, 2), - datetime.datetime(2015, 8, 2),] - ).tz_localize('MST') - sunrise = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 7, 19, 2), - datetime.datetime(2015, 8, 2, 5, 1, 26) - ]).tz_localize('MST').tolist() - sunset = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 16, 49, 10), - datetime.datetime(2015, 8, 2, 19, 11, 31) - ]).tz_localize('MST').tolist() - result = solarposition.get_sun_rise_set_transit(times, golden.latitude, + # test for Golden, CO compare to USNO + result = solarposition.get_sun_rise_set_transit(expected_rise_set.index, + golden.latitude, golden.longitude, delta_t=64.0) - frame = pd.DataFrame({'sunrise':sunrise, 'sunset':sunset}, index=times) result_rounded = pd.DataFrame(index=result.index) # need to iterate because to_datetime does not accept 2D data # the rounding fails on pandas < 0.17 @@ -176,7 +180,25 @@ def test_get_sun_rise_set_transit(): .tz_convert('MST')) del result_rounded['transit'] - assert_frame_equal(frame, result_rounded) + assert_frame_equal(expected_rise_set, result_rounded) + + +@requires_ephem +def test_ephem_next_rise_set(expected_rise_set): + # test for Golden, CO compare to USNO + result = solarposition.ephem_next_rise_set(expected_rise_set.index, + golden.latitude, + golden.longitude, + golden.altitude, + pressure=101325, + temperature=12) + result_rounded = pd.DataFrame(index=result.index) + for col, data in result.iteritems(): + result_rounded[col] = (pd.to_datetime( + np.floor(data.values.astype(np.int64) / 1e9)*1e9, utc=True) + .tz_convert('MST')) + + assert_frame_equal(expected_rise_set, result_rounded) @requires_ephem @@ -190,6 +212,7 @@ def test_pyephem_physical(expected_solpos): assert_frame_equal(expected_solpos.round(2), ephem_data[expected_solpos.columns].round(2)) + @requires_ephem def test_pyephem_physical_dst(expected_solpos): times = pd.date_range(datetime.datetime(2003,10,17,13,30,30), periods=1, @@ -201,6 +224,7 @@ def test_pyephem_physical_dst(expected_solpos): assert_frame_equal(expected_solpos.round(2), ephem_data[expected_solpos.columns].round(2)) + @requires_ephem def test_calc_time(): import pytz @@ -227,6 +251,7 @@ def test_calc_time(): assert_allclose((az.replace(second=0, microsecond=0) - epoch_dt).total_seconds(), actual_timestamp) + @requires_ephem def test_earthsun_distance(): times = pd.date_range(datetime.datetime(2003,10,17,13,30,30), From 0b9df182b9fc78521d446875cc9d887d8a1ab931 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Fri, 21 Sep 2018 15:22:03 -0600 Subject: [PATCH 02/33] Stickler issues --- pvlib/solarposition.py | 6 +++--- pvlib/test/test_solarposition.py | 9 ++++++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/pvlib/solarposition.py b/pvlib/solarposition.py index 86a2fff5c3..639495d17b 100644 --- a/pvlib/solarposition.py +++ b/pvlib/solarposition.py @@ -449,7 +449,7 @@ def _ephem_convert_to_seconds_and_microseconds(date): def _ephem_to_timezone(date, tzinfo): # utility from unreleased PyEphem 3.6.7.1 - """"Convert a PyEphem date into a timezone aware Python datetime representation.""" + """"Convert a PyEphem Date into a timezone aware python datetime""" seconds, microseconds = _ephem_convert_to_seconds_and_microseconds(date) date = dt.datetime.fromtimestamp(seconds, tzinfo) date = date.replace(microsecond=microseconds) @@ -524,8 +524,8 @@ def ephem_next_rise_set(time, latitude, longitude, altitude=0, next_sunrise.append(_ephem_to_timezone(obs.next_rising(sun), time.tz)) next_sunset.append(_ephem_to_timezone(obs.next_setting(sun), time.tz)) - return pd.DataFrame(index=time, data={'sunrise' : next_sunrise, - 'sunset' : next_sunset}) + return pd.DataFrame(index=time, data={'sunrise': next_sunrise, + 'sunset': next_sunset}) def pyephem(time, latitude, longitude, altitude=0, pressure=101325, diff --git a/pvlib/test/test_solarposition.py b/pvlib/test/test_solarposition.py index 2ed387be9f..e8ed7e3d4d 100644 --- a/pvlib/test/test_solarposition.py +++ b/pvlib/test/test_solarposition.py @@ -29,6 +29,7 @@ tol = 5 + @pytest.fixture() def expected_solpos(): return pd.DataFrame({'elevation': 39.872046, @@ -37,6 +38,7 @@ def expected_solpos(): 'apparent_elevation': 39.888378}, index=['2003-10-17T12:30:30Z']) + @pytest.fixture() def expected_solpos_multi(): return pd.DataFrame({'elevation': [39.872046, 39.505196], @@ -45,19 +47,20 @@ def expected_solpos_multi(): 'apparent_elevation': [39.888378, 39.521740]}, index=[['2003-10-17T12:30:30Z', '2003-10-18T12:30:30Z']]) + @pytest.fixture() def expected_rise_set(): # for Golden, CO, from USNO website times = pd.DatetimeIndex([datetime.datetime(2015, 1, 2), - datetime.datetime(2015, 8, 2),] - ).tz_localize('MST') + datetime.datetime(2015, 8, 2), + ]).tz_localize('MST') sunrise = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 7, 19, 2), datetime.datetime(2015, 8, 2, 5, 1, 26) ]).tz_localize('MST').tolist() sunset = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 16, 49, 10), datetime.datetime(2015, 8, 2, 19, 11, 31) ]).tz_localize('MST').tolist() - return pd.DataFrame({'sunrise':sunrise, 'sunset':sunset}, index=times) + return pd.DataFrame({'sunrise': sunrise, 'sunset': sunset}, index=times) # the physical tests are run at the same time as the NREL SPA test. From 86cdfa73b5aee6df503225570bf5bd4505edebe0 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Fri, 21 Sep 2018 15:49:49 -0600 Subject: [PATCH 03/33] Change test result and rounding to nearest minute --- pvlib/test/test_solarposition.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/pvlib/test/test_solarposition.py b/pvlib/test/test_solarposition.py index e8ed7e3d4d..d4da6d0e19 100644 --- a/pvlib/test/test_solarposition.py +++ b/pvlib/test/test_solarposition.py @@ -54,11 +54,11 @@ def expected_rise_set(): times = pd.DatetimeIndex([datetime.datetime(2015, 1, 2), datetime.datetime(2015, 8, 2), ]).tz_localize('MST') - sunrise = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 7, 19, 2), - datetime.datetime(2015, 8, 2, 5, 1, 26) + sunrise = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 7, 22, 0), + datetime.datetime(2015, 8, 2, 5, 1, 0) ]).tz_localize('MST').tolist() - sunset = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 16, 49, 10), - datetime.datetime(2015, 8, 2, 19, 11, 31) + sunset = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 16, 48, 0), + datetime.datetime(2015, 8, 2, 19, 13, 0) ]).tz_localize('MST').tolist() return pd.DataFrame({'sunrise': sunrise, 'sunset': sunset}, index=times) @@ -174,12 +174,14 @@ def test_get_sun_rise_set_transit(expected_rise_set): golden.latitude, golden.longitude, delta_t=64.0) + + # round to nearest minute result_rounded = pd.DataFrame(index=result.index) # need to iterate because to_datetime does not accept 2D data # the rounding fails on pandas < 0.17 for col, data in result.iteritems(): result_rounded[col] = (pd.to_datetime( - np.floor(data.values.astype(np.int64) / 1e9)*1e9, utc=True) + np.floor(data.values.astype(np.int64) / 60e9)*60e9, utc=True) .tz_convert('MST')) del result_rounded['transit'] @@ -195,10 +197,11 @@ def test_ephem_next_rise_set(expected_rise_set): golden.altitude, pressure=101325, temperature=12) + # round to nearest minute result_rounded = pd.DataFrame(index=result.index) for col, data in result.iteritems(): result_rounded[col] = (pd.to_datetime( - np.floor(data.values.astype(np.int64) / 1e9)*1e9, utc=True) + np.floor(data.values.astype(np.int64) / 60e9)*60e9, utc=True) .tz_convert('MST')) assert_frame_equal(expected_rise_set, result_rounded) From 06aef3f2da9b9651d0c645e449f4d991d82e899e Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Fri, 21 Sep 2018 16:15:14 -0600 Subject: [PATCH 04/33] Edit test condition, round not floor --- pvlib/test/test_solarposition.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pvlib/test/test_solarposition.py b/pvlib/test/test_solarposition.py index d4da6d0e19..23d9942f6c 100644 --- a/pvlib/test/test_solarposition.py +++ b/pvlib/test/test_solarposition.py @@ -164,7 +164,7 @@ def test_get_sun_rise_set_transit(expected_rise_set): # the rounding fails on pandas < 0.17 for col, data in result.iteritems(): result_rounded[col] = pd.to_datetime( - np.floor(data.values.astype(np.int64) / 1e9)*1e9, utc=True) + np.floor(data.values.astype(np.int64) / 1e9) * 1e9, utc=True) del result_rounded['transit'] assert_frame_equal(frame, result_rounded) @@ -181,7 +181,7 @@ def test_get_sun_rise_set_transit(expected_rise_set): # the rounding fails on pandas < 0.17 for col, data in result.iteritems(): result_rounded[col] = (pd.to_datetime( - np.floor(data.values.astype(np.int64) / 60e9)*60e9, utc=True) + np.round(data.values.astype(np.int64)), utc=True) .tz_convert('MST')) del result_rounded['transit'] @@ -189,7 +189,7 @@ def test_get_sun_rise_set_transit(expected_rise_set): @requires_ephem -def test_ephem_next_rise_set(expected_rise_set): +def test_next_rise_set_ephem(expected_rise_set): # test for Golden, CO compare to USNO result = solarposition.ephem_next_rise_set(expected_rise_set.index, golden.latitude, @@ -201,7 +201,7 @@ def test_ephem_next_rise_set(expected_rise_set): result_rounded = pd.DataFrame(index=result.index) for col, data in result.iteritems(): result_rounded[col] = (pd.to_datetime( - np.floor(data.values.astype(np.int64) / 60e9)*60e9, utc=True) + np.round(data.values.astype(np.int64)), utc=True) .tz_convert('MST')) assert_frame_equal(expected_rise_set, result_rounded) From a22d8466e51ffbaba068156dee0ac81600fa39c6 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Sat, 22 Sep 2018 11:03:48 -0600 Subject: [PATCH 05/33] Change explicit rounding to pandas .round('min') --- pvlib/test/test_solarposition.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/pvlib/test/test_solarposition.py b/pvlib/test/test_solarposition.py index 23d9942f6c..ad3b7ccab6 100644 --- a/pvlib/test/test_solarposition.py +++ b/pvlib/test/test_solarposition.py @@ -163,8 +163,7 @@ def test_get_sun_rise_set_transit(expected_rise_set): # need to iterate because to_datetime does not accept 2D data # the rounding fails on pandas < 0.17 for col, data in result.iteritems(): - result_rounded[col] = pd.to_datetime( - np.floor(data.values.astype(np.int64) / 1e9) * 1e9, utc=True) + result_rounded[col] = data.dt.round('min') del result_rounded['transit'] assert_frame_equal(frame, result_rounded) @@ -180,9 +179,7 @@ def test_get_sun_rise_set_transit(expected_rise_set): # need to iterate because to_datetime does not accept 2D data # the rounding fails on pandas < 0.17 for col, data in result.iteritems(): - result_rounded[col] = (pd.to_datetime( - np.round(data.values.astype(np.int64)), utc=True) - .tz_convert('MST')) + result_rounded[col] = data.dt.round('min').tz_convert('MST') del result_rounded['transit'] assert_frame_equal(expected_rise_set, result_rounded) From 719009d3be30dc7af2615eb3cd63bcda4e7c0ff9 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Sat, 22 Sep 2018 15:53:09 -0600 Subject: [PATCH 06/33] More test fixes --- pvlib/test/test_solarposition.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pvlib/test/test_solarposition.py b/pvlib/test/test_solarposition.py index ad3b7ccab6..cf310db52b 100644 --- a/pvlib/test/test_solarposition.py +++ b/pvlib/test/test_solarposition.py @@ -163,7 +163,7 @@ def test_get_sun_rise_set_transit(expected_rise_set): # need to iterate because to_datetime does not accept 2D data # the rounding fails on pandas < 0.17 for col, data in result.iteritems(): - result_rounded[col] = data.dt.round('min') + result_rounded[col] = data.dt.round('sec') del result_rounded['transit'] assert_frame_equal(frame, result_rounded) @@ -197,9 +197,7 @@ def test_next_rise_set_ephem(expected_rise_set): # round to nearest minute result_rounded = pd.DataFrame(index=result.index) for col, data in result.iteritems(): - result_rounded[col] = (pd.to_datetime( - np.round(data.values.astype(np.int64)), utc=True) - .tz_convert('MST')) + result_rounded[col] = data.dt.round('min').tz_convert('MST') assert_frame_equal(expected_rise_set, result_rounded) From aa4f1d62e3855fbe7d88230b72c35a8c34c90353 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Sat, 22 Sep 2018 16:08:25 -0600 Subject: [PATCH 07/33] Fix sec keyword --- pvlib/test/test_solarposition.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/test/test_solarposition.py b/pvlib/test/test_solarposition.py index cf310db52b..0de72e98b4 100644 --- a/pvlib/test/test_solarposition.py +++ b/pvlib/test/test_solarposition.py @@ -163,7 +163,7 @@ def test_get_sun_rise_set_transit(expected_rise_set): # need to iterate because to_datetime does not accept 2D data # the rounding fails on pandas < 0.17 for col, data in result.iteritems(): - result_rounded[col] = data.dt.round('sec') + result_rounded[col] = data.dt.round('1s') del result_rounded['transit'] assert_frame_equal(frame, result_rounded) From c08d213add553a066302a5bcb246f9fac85dd23a Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Sat, 22 Sep 2018 19:55:17 -0600 Subject: [PATCH 08/33] Add test for transit --- pvlib/test/test_solarposition.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/pvlib/test/test_solarposition.py b/pvlib/test/test_solarposition.py index 0de72e98b4..71f6e82ea2 100644 --- a/pvlib/test/test_solarposition.py +++ b/pvlib/test/test_solarposition.py @@ -55,12 +55,17 @@ def expected_rise_set(): datetime.datetime(2015, 8, 2), ]).tz_localize('MST') sunrise = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 7, 22, 0), - datetime.datetime(2015, 8, 2, 5, 1, 0) + datetime.datetime(2015, 8, 2, 5, 0, 0) ]).tz_localize('MST').tolist() sunset = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 16, 48, 0), datetime.datetime(2015, 8, 2, 19, 13, 0) ]).tz_localize('MST').tolist() - return pd.DataFrame({'sunrise': sunrise, 'sunset': sunset}, index=times) + transit = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 12, 5, 0), + datetime.datetime(2015, 8, 2, 12, 7, 0) + ]).tz_localize('MST').tolist() + return pd.DataFrame({'sunrise': sunrise, + 'sunset': sunset, + 'transit': transit}, index=times) # the physical tests are run at the same time as the NREL SPA test. @@ -177,11 +182,9 @@ def test_get_sun_rise_set_transit(expected_rise_set): # round to nearest minute result_rounded = pd.DataFrame(index=result.index) # need to iterate because to_datetime does not accept 2D data - # the rounding fails on pandas < 0.17 for col, data in result.iteritems(): result_rounded[col] = data.dt.round('min').tz_convert('MST') - del result_rounded['transit'] assert_frame_equal(expected_rise_set, result_rounded) @@ -198,7 +201,7 @@ def test_next_rise_set_ephem(expected_rise_set): result_rounded = pd.DataFrame(index=result.index) for col, data in result.iteritems(): result_rounded[col] = data.dt.round('min').tz_convert('MST') - + del expected_rise_set('transit') assert_frame_equal(expected_rise_set, result_rounded) From f192547df88b173f281ba65760d751bcd27618ef Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Sat, 22 Sep 2018 20:21:40 -0600 Subject: [PATCH 09/33] Fix del statement --- pvlib/test/test_solarposition.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/test/test_solarposition.py b/pvlib/test/test_solarposition.py index 71f6e82ea2..eb0053e4aa 100644 --- a/pvlib/test/test_solarposition.py +++ b/pvlib/test/test_solarposition.py @@ -201,7 +201,7 @@ def test_next_rise_set_ephem(expected_rise_set): result_rounded = pd.DataFrame(index=result.index) for col, data in result.iteritems(): result_rounded[col] = data.dt.round('min').tz_convert('MST') - del expected_rise_set('transit') + del expected_rise_set['transit'] assert_frame_equal(expected_rise_set, result_rounded) From 377658b7d41b16cb548845ab9d640b6b9e122ebd Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Sat, 22 Sep 2018 20:40:44 -0600 Subject: [PATCH 10/33] Fix column order --- pvlib/test/test_solarposition.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pvlib/test/test_solarposition.py b/pvlib/test/test_solarposition.py index eb0053e4aa..7adf309502 100644 --- a/pvlib/test/test_solarposition.py +++ b/pvlib/test/test_solarposition.py @@ -63,9 +63,10 @@ def expected_rise_set(): transit = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 12, 5, 0), datetime.datetime(2015, 8, 2, 12, 7, 0) ]).tz_localize('MST').tolist() - return pd.DataFrame({'sunrise': sunrise, + return pd.DataFrame({'transit': transit, + 'sunrise': sunrise, 'sunset': sunset, - 'transit': transit}, index=times) + }, index=times) # the physical tests are run at the same time as the NREL SPA test. From f338781693788b6df1b5f0e5628cbefbf898c739 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Sat, 22 Sep 2018 22:17:07 -0600 Subject: [PATCH 11/33] Change reference solution to NREL SPA website --- pvlib/test/test_solarposition.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pvlib/test/test_solarposition.py b/pvlib/test/test_solarposition.py index 7adf309502..50e17a1eca 100644 --- a/pvlib/test/test_solarposition.py +++ b/pvlib/test/test_solarposition.py @@ -50,7 +50,7 @@ def expected_solpos_multi(): @pytest.fixture() def expected_rise_set(): - # for Golden, CO, from USNO website + # for Golden, CO, from NREL SPA website times = pd.DatetimeIndex([datetime.datetime(2015, 1, 2), datetime.datetime(2015, 8, 2), ]).tz_localize('MST') @@ -58,7 +58,7 @@ def expected_rise_set(): datetime.datetime(2015, 8, 2, 5, 0, 0) ]).tz_localize('MST').tolist() sunset = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 16, 48, 0), - datetime.datetime(2015, 8, 2, 19, 13, 0) + datetime.datetime(2015, 8, 2, 19, 14, 0) ]).tz_localize('MST').tolist() transit = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 12, 5, 0), datetime.datetime(2015, 8, 2, 12, 7, 0) @@ -164,7 +164,7 @@ def test_get_sun_rise_set_transit(expected_rise_set): result = solarposition.get_sun_rise_set_transit(times, south.latitude, south.longitude, - delta_t=64.0) + delta_t=65.0) result_rounded = pd.DataFrame(index=result.index) # need to iterate because to_datetime does not accept 2D data # the rounding fails on pandas < 0.17 @@ -174,11 +174,11 @@ def test_get_sun_rise_set_transit(expected_rise_set): del result_rounded['transit'] assert_frame_equal(frame, result_rounded) - # test for Golden, CO compare to USNO + # test for Golden, CO compare to NREL SPA result = solarposition.get_sun_rise_set_transit(expected_rise_set.index, golden.latitude, golden.longitude, - delta_t=64.0) + delta_t=65.0) # round to nearest minute result_rounded = pd.DataFrame(index=result.index) @@ -191,7 +191,7 @@ def test_get_sun_rise_set_transit(expected_rise_set): @requires_ephem def test_next_rise_set_ephem(expected_rise_set): - # test for Golden, CO compare to USNO + # test for Golden, CO compare to NREL SPA result = solarposition.ephem_next_rise_set(expected_rise_set.index, golden.latitude, golden.longitude, From a3bedc0daacffcedb7920bda4686bf7ff5781d61 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Sat, 22 Sep 2018 22:33:00 -0600 Subject: [PATCH 12/33] Adjust pressure and temp for pyephem --- pvlib/test/test_solarposition.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pvlib/test/test_solarposition.py b/pvlib/test/test_solarposition.py index 50e17a1eca..7d3334457f 100644 --- a/pvlib/test/test_solarposition.py +++ b/pvlib/test/test_solarposition.py @@ -62,7 +62,7 @@ def expected_rise_set(): ]).tz_localize('MST').tolist() transit = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 12, 5, 0), datetime.datetime(2015, 8, 2, 12, 7, 0) - ]).tz_localize('MST').tolist() + ]).tz_localize('MST').tolist() return pd.DataFrame({'transit': transit, 'sunrise': sunrise, 'sunset': sunset, @@ -158,7 +158,7 @@ def test_get_sun_rise_set_transit(expected_rise_set): datetime.datetime(2004, 12, 4, 4, 38, 57)] ).tz_localize('UTC').tolist() sunset = pd.DatetimeIndex([datetime.datetime(1996, 7, 5, 17, 1, 4), - datetime.datetime(2004, 12, 4, 19, 2, 2)] + datetime.datetime(2004, 12, 4, 19, 2, 3)] ).tz_localize('UTC').tolist() frame = pd.DataFrame({'sunrise':sunrise, 'sunset':sunset}, index=times) @@ -196,8 +196,8 @@ def test_next_rise_set_ephem(expected_rise_set): golden.latitude, golden.longitude, golden.altitude, - pressure=101325, - temperature=12) + pressure=82000, + temperature=11) # round to nearest minute result_rounded = pd.DataFrame(index=result.index) for col, data in result.iteritems(): From 17564dd5f0f95dbcb77600f1cdb33db787fad7a8 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Sun, 23 Sep 2018 08:18:13 -0600 Subject: [PATCH 13/33] Add horizon kwarg for pyephem --- pvlib/solarposition.py | 14 ++++++++++---- pvlib/test/test_solarposition.py | 15 ++++++++------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/pvlib/solarposition.py b/pvlib/solarposition.py index 639495d17b..bd2a5cc126 100644 --- a/pvlib/solarposition.py +++ b/pvlib/solarposition.py @@ -456,7 +456,8 @@ def _ephem_to_timezone(date, tzinfo): return date -def _ephem_setup(latitude, longitude, altitude, pressure, temperature): +def _ephem_setup(latitude, longitude, altitude, pressure, temperature, + horizon): import ephem # initialize a PyEphem observer obs = ephem.Observer() @@ -465,14 +466,15 @@ def _ephem_setup(latitude, longitude, altitude, pressure, temperature): obs.elevation = altitude obs.pressure = pressure / 100. # convert to mBar obs.temp = temperature + obs.horizon = horizon # the PyEphem sun sun = ephem.Sun() return obs, sun -def ephem_next_rise_set(time, latitude, longitude, altitude=0, - pressure=101325, temperature=12): +def next_rise_set_ephem(time, latitude, longitude, altitude=0, + pressure=101325, temperature=12, horizon='0:00'): """ Calculate the next sunrise and sunset times using the PyEphem package. @@ -490,6 +492,10 @@ def ephem_next_rise_set(time, latitude, longitude, altitude=0, air pressure in Pascals. temperature : int or float, optional, default 12 air temperature in degrees C. + horizon : string, format +/-X:YY + arc degrees:arc minutes from geometrical horizon for sunrise and + sunset, e.g., horizon='-0:34' when the sun's upper edge crosses the + geometrical horizon Returns ------- @@ -515,7 +521,7 @@ def ephem_next_rise_set(time, latitude, longitude, altitude=0, time_utc = time obs, sun = _ephem_setup(latitude, longitude, altitude, - pressure, temperature) + pressure, temperature, horizon) # create lists of the next sunrise and sunset time localized to time.tz next_sunrise = [] next_sunset = [] diff --git a/pvlib/test/test_solarposition.py b/pvlib/test/test_solarposition.py index 7d3334457f..6a7bf616cc 100644 --- a/pvlib/test/test_solarposition.py +++ b/pvlib/test/test_solarposition.py @@ -50,7 +50,7 @@ def expected_solpos_multi(): @pytest.fixture() def expected_rise_set(): - # for Golden, CO, from NREL SPA website + # for Golden, CO, from NREL SPA and USNO websites times = pd.DatetimeIndex([datetime.datetime(2015, 1, 2), datetime.datetime(2015, 8, 2), ]).tz_localize('MST') @@ -58,7 +58,7 @@ def expected_rise_set(): datetime.datetime(2015, 8, 2, 5, 0, 0) ]).tz_localize('MST').tolist() sunset = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 16, 48, 0), - datetime.datetime(2015, 8, 2, 19, 14, 0) + datetime.datetime(2015, 8, 2, 19, 13, 0) ]).tz_localize('MST').tolist() transit = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 12, 5, 0), datetime.datetime(2015, 8, 2, 12, 7, 0) @@ -160,7 +160,7 @@ def test_get_sun_rise_set_transit(expected_rise_set): sunset = pd.DatetimeIndex([datetime.datetime(1996, 7, 5, 17, 1, 4), datetime.datetime(2004, 12, 4, 19, 2, 3)] ).tz_localize('UTC').tolist() - frame = pd.DataFrame({'sunrise':sunrise, 'sunset':sunset}, index=times) + frame = pd.DataFrame({'sunrise': sunrise, 'sunset': sunset}, index=times) result = solarposition.get_sun_rise_set_transit(times, south.latitude, south.longitude, @@ -191,13 +191,14 @@ def test_get_sun_rise_set_transit(expected_rise_set): @requires_ephem def test_next_rise_set_ephem(expected_rise_set): - # test for Golden, CO compare to NREL SPA - result = solarposition.ephem_next_rise_set(expected_rise_set.index, + # test for Golden, CO compare to USNO + result = solarposition.next_rise_set_ephem(expected_rise_set.index, golden.latitude, golden.longitude, golden.altitude, - pressure=82000, - temperature=11) + pressure=0, + temperature=11, + horizon='-0:34') # round to nearest minute result_rounded = pd.DataFrame(index=result.index) for col, data in result.iteritems(): From c29ed0652ee3b0b2bd42717138089bef853a9592 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Sun, 23 Sep 2018 12:40:59 -0600 Subject: [PATCH 14/33] Separate rise, set test for spa and pyephem --- pvlib/test/test_solarposition.py | 43 +++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/pvlib/test/test_solarposition.py b/pvlib/test/test_solarposition.py index 6a7bf616cc..96928bec2b 100644 --- a/pvlib/test/test_solarposition.py +++ b/pvlib/test/test_solarposition.py @@ -49,8 +49,29 @@ def expected_solpos_multi(): @pytest.fixture() -def expected_rise_set(): - # for Golden, CO, from NREL SPA and USNO websites +def expected_rise_set_spa(): + # for Golden, CO, from NREL SPA website + times = pd.DatetimeIndex([datetime.datetime(2015, 1, 2), + datetime.datetime(2015, 8, 2), + ]).tz_localize('MST') + sunrise = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 7, 21, 55), + datetime.datetime(2015, 8, 2, 5, 0, 27) + ]).tz_localize('MST').tolist() + sunset = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 16, 47, 43), + datetime.datetime(2015, 8, 2, 19, 12, 15) + ]).tz_localize('MST').tolist() + transit = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 12, 4, 45), + datetime.datetime(2015, 8, 2, 12, 6, 58) + ]).tz_localize('MST').tolist() + return pd.DataFrame({'transit': transit, + 'sunrise': sunrise, + 'sunset': sunset, + }, index=times) + + +@pytest.fixture() +def expected_rise_set_ephem(): + # for Golden, CO, from USNO websites times = pd.DatetimeIndex([datetime.datetime(2015, 1, 2), datetime.datetime(2015, 8, 2), ]).tz_localize('MST') @@ -67,8 +88,6 @@ def expected_rise_set(): 'sunrise': sunrise, 'sunset': sunset, }, index=times) - - # the physical tests are run at the same time as the NREL SPA test. # pyephem reproduces the NREL result to 2 decimal places. # this doesn't mean that one code is better than the other. @@ -149,7 +168,7 @@ def test_spa_python_numba_physical_dst(expected_solpos): @needs_pandas_0_17 -def test_get_sun_rise_set_transit(expected_rise_set): +def test_get_sun_rise_set_transit(expected_rise_set_spa): south = Location(-35.0, 0.0, tz='UTC') times = pd.DatetimeIndex([datetime.datetime(1996, 7, 5, 0), datetime.datetime(2004, 12, 4, 0)] @@ -175,7 +194,7 @@ def test_get_sun_rise_set_transit(expected_rise_set): assert_frame_equal(frame, result_rounded) # test for Golden, CO compare to NREL SPA - result = solarposition.get_sun_rise_set_transit(expected_rise_set.index, + result = solarposition.get_sun_rise_set_transit(expected_rise_set_spa.index, golden.latitude, golden.longitude, delta_t=65.0) @@ -184,15 +203,15 @@ def test_get_sun_rise_set_transit(expected_rise_set): result_rounded = pd.DataFrame(index=result.index) # need to iterate because to_datetime does not accept 2D data for col, data in result.iteritems(): - result_rounded[col] = data.dt.round('min').tz_convert('MST') + result_rounded[col] = data.dt.round('s').tz_convert('MST') - assert_frame_equal(expected_rise_set, result_rounded) + assert_frame_equal(expected_rise_set_spa, result_rounded) @requires_ephem -def test_next_rise_set_ephem(expected_rise_set): +def test_next_rise_set_ephem(expected_rise_set_ephem): # test for Golden, CO compare to USNO - result = solarposition.next_rise_set_ephem(expected_rise_set.index, + result = solarposition.next_rise_set_ephem(expected_rise_set_ephem.index, golden.latitude, golden.longitude, golden.altitude, @@ -203,8 +222,8 @@ def test_next_rise_set_ephem(expected_rise_set): result_rounded = pd.DataFrame(index=result.index) for col, data in result.iteritems(): result_rounded[col] = data.dt.round('min').tz_convert('MST') - del expected_rise_set['transit'] - assert_frame_equal(expected_rise_set, result_rounded) + del expected_rise_set_ephem['transit'] + assert_frame_equal(expected_rise_set_ephem, result_rounded) @requires_ephem From a3d143d4cd35e00b8d6baf6b6526c29362e119e5 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Sun, 23 Sep 2018 14:11:53 -0600 Subject: [PATCH 15/33] Add horizon kwarg to pyephem, fix test condition for spa --- pvlib/solarposition.py | 8 ++++++-- pvlib/test/test_solarposition.py | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pvlib/solarposition.py b/pvlib/solarposition.py index bd2a5cc126..1c297d41fb 100644 --- a/pvlib/solarposition.py +++ b/pvlib/solarposition.py @@ -535,7 +535,7 @@ def next_rise_set_ephem(time, latitude, longitude, altitude=0, def pyephem(time, latitude, longitude, altitude=0, pressure=101325, - temperature=12): + temperature=12, horizon='+0:00'): """ Calculate the solar position using the PyEphem package. @@ -553,6 +553,10 @@ def pyephem(time, latitude, longitude, altitude=0, pressure=101325, air pressure in Pascals. temperature : int or float, optional, default 12 air temperature in degrees C. + horizon : string, optional, default '+0:00' + arc degrees:arc minutes from geometrical horizon for sunrise and + sunset, e.g., horizon='-0:34' when the sun's upper edge crosses the + geometrical horizon Returns ------- @@ -582,7 +586,7 @@ def pyephem(time, latitude, longitude, altitude=0, pressure=101325, sun_coords = pd.DataFrame(index=time) obs, sun = _ephem_setup(latitude, longitude, altitude, - pressure, temperature) + pressure, temperature, horizon) # make and fill lists of the sun's altitude and azimuth # this is the pressure and temperature corrected apparent alt/az. diff --git a/pvlib/test/test_solarposition.py b/pvlib/test/test_solarposition.py index 96928bec2b..ef707fb7c6 100644 --- a/pvlib/test/test_solarposition.py +++ b/pvlib/test/test_solarposition.py @@ -58,7 +58,7 @@ def expected_rise_set_spa(): datetime.datetime(2015, 8, 2, 5, 0, 27) ]).tz_localize('MST').tolist() sunset = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 16, 47, 43), - datetime.datetime(2015, 8, 2, 19, 12, 15) + datetime.datetime(2015, 8, 2, 19, 13, 58) ]).tz_localize('MST').tolist() transit = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 12, 4, 45), datetime.datetime(2015, 8, 2, 12, 6, 58) From 99a75a0992081417f7a6c32c73696a46385a631e Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Sun, 23 Sep 2018 14:52:11 -0600 Subject: [PATCH 16/33] Add horizon kwarg to calc_time --- pvlib/solarposition.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pvlib/solarposition.py b/pvlib/solarposition.py index 1c297d41fb..2608e9f241 100644 --- a/pvlib/solarposition.py +++ b/pvlib/solarposition.py @@ -798,7 +798,8 @@ def ephemeris(time, latitude, longitude, pressure=101325, temperature=12): def calc_time(lower_bound, upper_bound, latitude, longitude, attribute, value, - altitude=0, pressure=101325, temperature=12, xtol=1.0e-12): + altitude=0, pressure=101325, temperature=12, horizon='+0:00', + xtol=1.0e-12): """ Calculate the time between lower_bound and upper_bound where the attribute is equal to value. Uses PyEphem for @@ -823,6 +824,10 @@ def calc_time(lower_bound, upper_bound, latitude, longitude, attribute, value, atmospheric correction. temperature : int or float, optional, default 12 Air temperature in degrees C. + horizon : string, optional, default '+0:00' + arc degrees:arc minutes from geometrical horizon for sunrise and + sunset, e.g., horizon='-0:34' when the sun's upper edge crosses the + geometrical horizon xtol : float, optional, default 1.0e-12 The allowed error in the result from value @@ -845,7 +850,7 @@ def calc_time(lower_bound, upper_bound, latitude, longitude, attribute, value, raise ImportError('The calc_time function requires scipy') obs, sun = _ephem_setup(latitude, longitude, altitude, - pressure, temperature) + pressure, temperature, horizon) def compute_attr(thetime, target, attr): obs.date = thetime From 95187d523e3aa6eb8c42ffd869ba87c499ac1a72 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Mon, 24 Sep 2018 10:20:20 -0600 Subject: [PATCH 17/33] Extend tests for next_rise_set_ephem --- pvlib/test/test_solarposition.py | 64 ++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 4 deletions(-) diff --git a/pvlib/test/test_solarposition.py b/pvlib/test/test_solarposition.py index ef707fb7c6..be8ba3a0d1 100644 --- a/pvlib/test/test_solarposition.py +++ b/pvlib/test/test_solarposition.py @@ -65,29 +65,34 @@ def expected_rise_set_spa(): ]).tz_localize('MST').tolist() return pd.DataFrame({'transit': transit, 'sunrise': sunrise, - 'sunset': sunset, - }, index=times) + 'sunset': sunset}, + index=times, + dtype='datetime64[ns]') @pytest.fixture() def expected_rise_set_ephem(): # for Golden, CO, from USNO websites times = pd.DatetimeIndex([datetime.datetime(2015, 1, 2), + datetime.datetime(2015, 1, 3), datetime.datetime(2015, 8, 2), ]).tz_localize('MST') sunrise = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 7, 22, 0), + datetime.datetime(2015, 1, 3, 7, 22, 0), datetime.datetime(2015, 8, 2, 5, 0, 0) ]).tz_localize('MST').tolist() sunset = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 16, 48, 0), + datetime.datetime(2015, 1, 3, 16, 49, 0), datetime.datetime(2015, 8, 2, 19, 13, 0) ]).tz_localize('MST').tolist() transit = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 12, 5, 0), + datetime.datetime(2015, 1, 3, 12, 5, 0), datetime.datetime(2015, 8, 2, 12, 7, 0) ]).tz_localize('MST').tolist() return pd.DataFrame({'transit': transit, 'sunrise': sunrise, - 'sunset': sunset, - }, index=times) + 'sunset': sunset}, + index=times) # the physical tests are run at the same time as the NREL SPA test. # pyephem reproduces the NREL result to 2 decimal places. # this doesn't mean that one code is better than the other. @@ -225,6 +230,57 @@ def test_next_rise_set_ephem(expected_rise_set_ephem): del expected_rise_set_ephem['transit'] assert_frame_equal(expected_rise_set_ephem, result_rounded) + # test with times + times = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 3, 0, 0), + datetime.datetime(2015, 1, 2, 10, 15, 0), + datetime.datetime(2015, 1, 2, 15, 3, 0), + datetime.datetime(2015, 1, 2, 21, 6, 7) + ]).tz_localize('MST') + expected = pd.DataFrame(index=times, + columns=['sunrise', 'sunset'], + dtype='datetime64[ns]') + expected['sunrise'] = pd.Series(index=times, data = \ + [expected_rise_set_ephem.loc[datetime.datetime(2015, 1, 2), 'sunrise'], + expected_rise_set_ephem.loc[datetime.datetime(2015, 1, 3), 'sunrise'], + expected_rise_set_ephem.loc[datetime.datetime(2015, 1, 3), 'sunrise'], + expected_rise_set_ephem.loc[datetime.datetime(2015, 1, 3), 'sunrise']]) + expected['sunset'] = pd.Series(index=times, data = \ + [expected_rise_set_ephem.loc[datetime.datetime(2015, 1, 2), 'sunset'], + expected_rise_set_ephem.loc[datetime.datetime(2015, 1, 2), 'sunset'], + expected_rise_set_ephem.loc[datetime.datetime(2015, 1, 2), 'sunset'], + expected_rise_set_ephem.loc[datetime.datetime(2015, 1, 3), 'sunset']]) + + result = solarposition.next_rise_set_ephem(times, + golden.latitude, + golden.longitude, + golden.altitude, + pressure=0, + temperature=11, + horizon='-0:34') + # round to nearest minute + result_rounded = pd.DataFrame(index=result.index) + for col, data in result.iteritems(): + result_rounded[col] = data.dt.round('min').tz_convert('MST') + assert_frame_equal(expected, result_rounded) + + # test with different timezone + times = times.tz_convert('UTC') + expected = expected.tz_convert('UTC') + for col, data in expected.iteritems(): + expected[col] = data.dt.tz_convert('UTC') + result = solarposition.next_rise_set_ephem(times, + golden.latitude, + golden.longitude, + golden.altitude, + pressure=0, + temperature=11, + horizon='-0:34') + # round to nearest minute + result_rounded = pd.DataFrame(index=result.index) + for col, data in result.iteritems(): + result_rounded[col] = data.dt.round('min').tz_convert(times.tz) + assert_frame_equal(expected, result_rounded) + @requires_ephem def test_pyephem_physical(expected_solpos): From e318bad4b77a6d6f25de1e1e306caf8c8bb703b6 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Mon, 24 Sep 2018 11:02:36 -0600 Subject: [PATCH 18/33] Remove dtype from expected...spa --- pvlib/test/test_solarposition.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/pvlib/test/test_solarposition.py b/pvlib/test/test_solarposition.py index be8ba3a0d1..1275313c86 100644 --- a/pvlib/test/test_solarposition.py +++ b/pvlib/test/test_solarposition.py @@ -66,8 +66,7 @@ def expected_rise_set_spa(): return pd.DataFrame({'transit': transit, 'sunrise': sunrise, 'sunset': sunset}, - index=times, - dtype='datetime64[ns]') + index=times) @pytest.fixture() @@ -199,10 +198,9 @@ def test_get_sun_rise_set_transit(expected_rise_set_spa): assert_frame_equal(frame, result_rounded) # test for Golden, CO compare to NREL SPA - result = solarposition.get_sun_rise_set_transit(expected_rise_set_spa.index, - golden.latitude, - golden.longitude, - delta_t=65.0) + result = solarposition.get_sun_rise_set_transit( + expected_rise_set_spa.index, golden.latitude, golden.longitude, + delta_t=65.0) # round to nearest minute result_rounded = pd.DataFrame(index=result.index) @@ -239,12 +237,12 @@ def test_next_rise_set_ephem(expected_rise_set_ephem): expected = pd.DataFrame(index=times, columns=['sunrise', 'sunset'], dtype='datetime64[ns]') - expected['sunrise'] = pd.Series(index=times, data = \ + expected['sunrise'] = pd.Series(index=times, data = [expected_rise_set_ephem.loc[datetime.datetime(2015, 1, 2), 'sunrise'], expected_rise_set_ephem.loc[datetime.datetime(2015, 1, 3), 'sunrise'], expected_rise_set_ephem.loc[datetime.datetime(2015, 1, 3), 'sunrise'], expected_rise_set_ephem.loc[datetime.datetime(2015, 1, 3), 'sunrise']]) - expected['sunset'] = pd.Series(index=times, data = \ + expected['sunset'] = pd.Series(index=times, data = [expected_rise_set_ephem.loc[datetime.datetime(2015, 1, 2), 'sunset'], expected_rise_set_ephem.loc[datetime.datetime(2015, 1, 2), 'sunset'], expected_rise_set_ephem.loc[datetime.datetime(2015, 1, 2), 'sunset'], From a88a5b6218a6af4a77395d7d1de2957693e7e240 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Fri, 28 Sep 2018 15:42:35 -0600 Subject: [PATCH 19/33] Add previous rise/set capability --- pvlib/solarposition.py | 93 +++++++++++++++++++++++++--- pvlib/test/test_solarposition.py | 103 +++++++++++++++++++++---------- 2 files changed, 157 insertions(+), 39 deletions(-) diff --git a/pvlib/solarposition.py b/pvlib/solarposition.py index 2608e9f241..c6895f4c0e 100644 --- a/pvlib/solarposition.py +++ b/pvlib/solarposition.py @@ -473,11 +473,86 @@ def _ephem_setup(latitude, longitude, altitude, pressure, temperature, return obs, sun -def next_rise_set_ephem(time, latitude, longitude, altitude=0, - pressure=101325, temperature=12, horizon='0:00'): +def get_rise_set_ephem(time, latitude, longitude, + next_or_previous='next', + altitude=0, + pressure=101325, temperature=12, horizon='0:00'): """ Calculate the next sunrise and sunset times using the PyEphem package. + Parameters + ---------- + time : pandas.DatetimeIndex + Localized or UTC. + latitude : float + positive is north of 0 + longitude : float + positive is east of 0 + next_or_previous : str + 'next' or 'previous' sunrise and sunset relative to time + altitude : float, default 0 + distance above sea level in meters. + pressure : int or float, optional, default 101325 + air pressure in Pascals. + temperature : int or float, optional, default 12 + air temperature in degrees C. + horizon : string, format +/-X:YY + arc degrees:arc minutes from geometrical horizon for sunrise and + sunset, e.g., horizon='-0:34' when the sun's upper edge crosses the + geometrical horizon + + Returns + ------- + pandas.DataFrame + index is the same as input time.index + columns are 'sunrise' and 'sunset', times are localized to the + timezone time.tz + + See also + -------- + pyephem + """ + + try: + import ephem + except ImportError: + raise ImportError('PyEphem must be installed') + + # if localized, convert to UTC. otherwise, assume UTC. + try: + time_utc = time.tz_convert('UTC') + except TypeError: + time_utc = time + + obs, sun = _ephem_setup(latitude, longitude, altitude, + pressure, temperature, horizon) + # create lists of sunrise and sunset time localized to time.tz + if next_or_previous.lower() == 'next': + rising = obs.next_rising + setting = obs.next_setting + elif next_or_previous.lower() == 'previous': + rising = obs.previous_rising + setting = obs.previous_setting + else: + raise ValueError("next_or_previous must be either 'next' or" + + " 'previous'") + + sunrise = [] + sunset = [] + for thetime in time_utc: + obs.date = ephem.Date(thetime) + sunrise.append(_ephem_to_timezone(rising(sun), time.tz)) + sunset.append(_ephem_to_timezone(setting(sun), time.tz)) + + return pd.DataFrame(index=time, data={'sunrise': sunrise, + 'sunset': sunset}) + + +def previous_rise_set_ephem(time, latitude, longitude, altitude=0, + pressure=101325, temperature=12, horizon='0:00'): + """ + Calculate the previous sunrise and sunset times using the PyEphem package. + Parameters ---------- time : pandas.DatetimeIndex @@ -523,15 +598,17 @@ def next_rise_set_ephem(time, latitude, longitude, altitude=0, obs, sun = _ephem_setup(latitude, longitude, altitude, pressure, temperature, horizon) # create lists of the next sunrise and sunset time localized to time.tz - next_sunrise = [] - next_sunset = [] + previous_sunrise = [] + previous_sunset = [] for thetime in time_utc: obs.date = ephem.Date(thetime) - next_sunrise.append(_ephem_to_timezone(obs.next_rising(sun), time.tz)) - next_sunset.append(_ephem_to_timezone(obs.next_setting(sun), time.tz)) + previous_sunrise.append(_ephem_to_timezone(obs.previous_rising(sun), + time.tz)) + previous_sunset.append(_ephem_to_timezone(obs.previous_setting(sun), + time.tz)) - return pd.DataFrame(index=time, data={'sunrise': next_sunrise, - 'sunset': next_sunset}) + return pd.DataFrame(index=time, data={'sunrise': previous_sunrise, + 'sunset': previous_sunset}) def pyephem(time, latitude, longitude, altitude=0, pressure=101325, diff --git a/pvlib/test/test_solarposition.py b/pvlib/test/test_solarposition.py index 1275313c86..2ff18c2f20 100644 --- a/pvlib/test/test_solarposition.py +++ b/pvlib/test/test_solarposition.py @@ -72,19 +72,23 @@ def expected_rise_set_spa(): @pytest.fixture() def expected_rise_set_ephem(): # for Golden, CO, from USNO websites - times = pd.DatetimeIndex([datetime.datetime(2015, 1, 2), + times = pd.DatetimeIndex([datetime.datetime(2015, 1, 1), + datetime.datetime(2015, 1, 2), datetime.datetime(2015, 1, 3), datetime.datetime(2015, 8, 2), ]).tz_localize('MST') sunrise = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 7, 22, 0), + datetime.datetime(2015, 1, 2, 7, 22, 0), datetime.datetime(2015, 1, 3, 7, 22, 0), datetime.datetime(2015, 8, 2, 5, 0, 0) ]).tz_localize('MST').tolist() - sunset = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 16, 48, 0), + sunset = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 16, 47, 0), + datetime.datetime(2015, 1, 2, 16, 48, 0), datetime.datetime(2015, 1, 3, 16, 49, 0), datetime.datetime(2015, 8, 2, 19, 13, 0) ]).tz_localize('MST').tolist() - transit = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 12, 5, 0), + transit = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 12, 4, 0), + datetime.datetime(2015, 1, 2, 12, 5, 0), datetime.datetime(2015, 1, 3, 12, 5, 0), datetime.datetime(2015, 8, 2, 12, 7, 0) ]).tz_localize('MST').tolist() @@ -199,8 +203,8 @@ def test_get_sun_rise_set_transit(expected_rise_set_spa): # test for Golden, CO compare to NREL SPA result = solarposition.get_sun_rise_set_transit( - expected_rise_set_spa.index, golden.latitude, golden.longitude, - delta_t=65.0) + expected_rise_set_spa.index, golden.latitude, golden.longitude, + delta_t=65.0) # round to nearest minute result_rounded = pd.DataFrame(index=result.index) @@ -212,15 +216,16 @@ def test_get_sun_rise_set_transit(expected_rise_set_spa): @requires_ephem -def test_next_rise_set_ephem(expected_rise_set_ephem): - # test for Golden, CO compare to USNO - result = solarposition.next_rise_set_ephem(expected_rise_set_ephem.index, - golden.latitude, - golden.longitude, - golden.altitude, - pressure=0, - temperature=11, - horizon='-0:34') +def test_get_rise_set_ephem(expected_rise_set_ephem): + # test for Golden, CO compare to USNO, using local midnight + result = solarposition.get_rise_set_ephem(expected_rise_set_ephem.index, + golden.latitude, + golden.longitude, + next_or_previous='next', + golden.altitude, + pressure=0, + temperature=11, + horizon='-0:34') # round to nearest minute result_rounded = pd.DataFrame(index=result.index) for col, data in result.iteritems(): @@ -228,7 +233,7 @@ def test_next_rise_set_ephem(expected_rise_set_ephem): del expected_rise_set_ephem['transit'] assert_frame_equal(expected_rise_set_ephem, result_rounded) - # test with times + # test next sunrise/sunset with times times = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 3, 0, 0), datetime.datetime(2015, 1, 2, 10, 15, 0), datetime.datetime(2015, 1, 2, 15, 3, 0), @@ -237,24 +242,59 @@ def test_next_rise_set_ephem(expected_rise_set_ephem): expected = pd.DataFrame(index=times, columns=['sunrise', 'sunset'], dtype='datetime64[ns]') - expected['sunrise'] = pd.Series(index=times, data = + expected['sunrise'] = pd.Series(index=times, data= [expected_rise_set_ephem.loc[datetime.datetime(2015, 1, 2), 'sunrise'], expected_rise_set_ephem.loc[datetime.datetime(2015, 1, 3), 'sunrise'], expected_rise_set_ephem.loc[datetime.datetime(2015, 1, 3), 'sunrise'], expected_rise_set_ephem.loc[datetime.datetime(2015, 1, 3), 'sunrise']]) - expected['sunset'] = pd.Series(index=times, data = + expected['sunset'] = pd.Series(index=times, data= [expected_rise_set_ephem.loc[datetime.datetime(2015, 1, 2), 'sunset'], expected_rise_set_ephem.loc[datetime.datetime(2015, 1, 2), 'sunset'], expected_rise_set_ephem.loc[datetime.datetime(2015, 1, 2), 'sunset'], expected_rise_set_ephem.loc[datetime.datetime(2015, 1, 3), 'sunset']]) - result = solarposition.next_rise_set_ephem(times, - golden.latitude, - golden.longitude, - golden.altitude, - pressure=0, - temperature=11, - horizon='-0:34') + result = solarposition.get_rise_set_ephem(times, + golden.latitude, + golden.longitude, + next_or_previous='next', + golden.altitude, + pressure=0, + temperature=11, + horizon='-0:34') + # round to nearest minute + result_rounded = pd.DataFrame(index=result.index) + for col, data in result.iteritems(): + result_rounded[col] = data.dt.round('min').tz_convert('MST') + assert_frame_equal(expected, result_rounded) + + # test previous sunrise/sunset with times + times = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 3, 0, 0), + datetime.datetime(2015, 1, 2, 10, 15, 0), + datetime.datetime(2015, 1, 3, 3, 0, 0), + datetime.datetime(2015, 1, 3, 13, 6, 7) + ]).tz_localize('MST') + expected = pd.DataFrame(index=times, + columns=['sunrise', 'sunset'], + dtype='datetime64[ns]') + expected['sunrise'] = pd.Series(index=times, data= + [expected_rise_set_ephem.loc[datetime.datetime(2015, 1, 1), 'sunrise'], + expected_rise_set_ephem.loc[datetime.datetime(2015, 1, 2), 'sunrise'], + expected_rise_set_ephem.loc[datetime.datetime(2015, 1, 2), 'sunrise'], + expected_rise_set_ephem.loc[datetime.datetime(2015, 1, 3), 'sunrise']]) + expected['sunset'] = pd.Series(index=times, data= + [expected_rise_set_ephem.loc[datetime.datetime(2015, 1, 1), 'sunset'], + expected_rise_set_ephem.loc[datetime.datetime(2015, 1, 1), 'sunset'], + expected_rise_set_ephem.loc[datetime.datetime(2015, 1, 2), 'sunset'], + expected_rise_set_ephem.loc[datetime.datetime(2015, 1, 2), 'sunset']]) + + result = solarposition.get_rise_set_ephem(times, + golden.latitude, + golden.longitude, + next_or_previous='previous', + golden.altitude, + pressure=0, + temperature=11, + horizon='-0:34') # round to nearest minute result_rounded = pd.DataFrame(index=result.index) for col, data in result.iteritems(): @@ -266,13 +306,14 @@ def test_next_rise_set_ephem(expected_rise_set_ephem): expected = expected.tz_convert('UTC') for col, data in expected.iteritems(): expected[col] = data.dt.tz_convert('UTC') - result = solarposition.next_rise_set_ephem(times, - golden.latitude, - golden.longitude, - golden.altitude, - pressure=0, - temperature=11, - horizon='-0:34') + result = solarposition.get_rise_set_ephem(times, + golden.latitude, + golden.longitude, + next_or_previous='next', + golden.altitude, + pressure=0, + temperature=11, + horizon='-0:34') # round to nearest minute result_rounded = pd.DataFrame(index=result.index) for col, data in result.iteritems(): From 2121aa0ac80561bba7e2bf1050f3e6e6b629c261 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Fri, 28 Sep 2018 15:46:49 -0600 Subject: [PATCH 20/33] style fixes --- pvlib/test/test_solarposition.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pvlib/test/test_solarposition.py b/pvlib/test/test_solarposition.py index 2ff18c2f20..f292e62274 100644 --- a/pvlib/test/test_solarposition.py +++ b/pvlib/test/test_solarposition.py @@ -222,7 +222,7 @@ def test_get_rise_set_ephem(expected_rise_set_ephem): golden.latitude, golden.longitude, next_or_previous='next', - golden.altitude, + altitude=golden.altitude, pressure=0, temperature=11, horizon='-0:34') @@ -257,7 +257,7 @@ def test_get_rise_set_ephem(expected_rise_set_ephem): golden.latitude, golden.longitude, next_or_previous='next', - golden.altitude, + altitude=golden.altitude, pressure=0, temperature=11, horizon='-0:34') @@ -291,7 +291,7 @@ def test_get_rise_set_ephem(expected_rise_set_ephem): golden.latitude, golden.longitude, next_or_previous='previous', - golden.altitude, + altitude=golden.altitude, pressure=0, temperature=11, horizon='-0:34') @@ -310,7 +310,7 @@ def test_get_rise_set_ephem(expected_rise_set_ephem): golden.latitude, golden.longitude, next_or_previous='next', - golden.altitude, + altitude=golden.altitude, pressure=0, temperature=11, horizon='-0:34') From c75b10e47798d734dc5478f7ead1888145fbdc6d Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Fri, 28 Sep 2018 16:42:38 -0600 Subject: [PATCH 21/33] Fix expected result --- pvlib/test/test_solarposition.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pvlib/test/test_solarposition.py b/pvlib/test/test_solarposition.py index f292e62274..5e564ef863 100644 --- a/pvlib/test/test_solarposition.py +++ b/pvlib/test/test_solarposition.py @@ -77,17 +77,17 @@ def expected_rise_set_ephem(): datetime.datetime(2015, 1, 3), datetime.datetime(2015, 8, 2), ]).tz_localize('MST') - sunrise = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 7, 22, 0), + sunrise = pd.DatetimeIndex([datetime.datetime(2015, 1, 1, 7, 22, 0), datetime.datetime(2015, 1, 2, 7, 22, 0), datetime.datetime(2015, 1, 3, 7, 22, 0), datetime.datetime(2015, 8, 2, 5, 0, 0) ]).tz_localize('MST').tolist() - sunset = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 16, 47, 0), + sunset = pd.DatetimeIndex([datetime.datetime(2015, 1, 1, 16, 47, 0), datetime.datetime(2015, 1, 2, 16, 48, 0), datetime.datetime(2015, 1, 3, 16, 49, 0), datetime.datetime(2015, 8, 2, 19, 13, 0) ]).tz_localize('MST').tolist() - transit = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 12, 4, 0), + transit = pd.DatetimeIndex([datetime.datetime(2015, 1, 1, 12, 4, 0), datetime.datetime(2015, 1, 2, 12, 5, 0), datetime.datetime(2015, 1, 3, 12, 5, 0), datetime.datetime(2015, 8, 2, 12, 7, 0) From f85a46a75c718f1d9ae6da3a2673c46c85f79110 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Fri, 28 Sep 2018 21:26:14 -0600 Subject: [PATCH 22/33] Fix timezone test --- pvlib/test/test_solarposition.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pvlib/test/test_solarposition.py b/pvlib/test/test_solarposition.py index 5e564ef863..27c516577e 100644 --- a/pvlib/test/test_solarposition.py +++ b/pvlib/test/test_solarposition.py @@ -303,13 +303,13 @@ def test_get_rise_set_ephem(expected_rise_set_ephem): # test with different timezone times = times.tz_convert('UTC') - expected = expected.tz_convert('UTC') + expected = expected.tz_convert('UTC') # resuse result from previous for col, data in expected.iteritems(): expected[col] = data.dt.tz_convert('UTC') result = solarposition.get_rise_set_ephem(times, golden.latitude, golden.longitude, - next_or_previous='next', + next_or_previous='previous', altitude=golden.altitude, pressure=0, temperature=11, From 6238321847c679fa0dd85fd7175de1acb5b81a14 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Sat, 29 Sep 2018 09:40:36 -0600 Subject: [PATCH 23/33] Documentation updates --- docs/sphinx/source/api.rst | 1 + docs/sphinx/source/whatsnew/v0.6.1.rst | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/sphinx/source/api.rst b/docs/sphinx/source/api.rst index 0b9a8e20e4..ce28728517 100644 --- a/docs/sphinx/source/api.rst +++ b/docs/sphinx/source/api.rst @@ -55,6 +55,7 @@ Additional functions for quantities closely related to solar position. solarposition.calc_time solarposition.pyephem_earthsun_distance solarposition.nrel_earthsun_distance + solarposition.get_rise_set_ephem spa.calculate_deltat The spa module contains the implementation of the built-in NREL SPA diff --git a/docs/sphinx/source/whatsnew/v0.6.1.rst b/docs/sphinx/source/whatsnew/v0.6.1.rst index 37f8ee312a..8f0aefcc0d 100644 --- a/docs/sphinx/source/whatsnew/v0.6.1.rst +++ b/docs/sphinx/source/whatsnew/v0.6.1.rst @@ -16,6 +16,7 @@ API Changes Enhancements ~~~~~~~~~~~~ +* :func:`~pvlib.solarposition.get_rise_set_ephem` returns sunrise and sunset times using pyephem Bug fixes @@ -29,4 +30,5 @@ Testing Contributors ~~~~~~~~~~~~ -* Will Holmgren (:ghuser:`wholmgren`) \ No newline at end of file +* Will Holmgren (:ghuser:`wholmgren`) +* Cliff Hansen (:ghuser:`cwhanse`) From 36180223fc819106b11dd8fae8f9e898094c7479 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Sat, 29 Sep 2018 09:41:24 -0600 Subject: [PATCH 24/33] Fix spacing --- pvlib/solarposition.py | 63 ------------------------------------------ 1 file changed, 63 deletions(-) diff --git a/pvlib/solarposition.py b/pvlib/solarposition.py index c6895f4c0e..f398ba83eb 100644 --- a/pvlib/solarposition.py +++ b/pvlib/solarposition.py @@ -548,69 +548,6 @@ def get_rise_set_ephem(time, latitude, longitude, 'sunset': sunset}) -def previous_rise_set_ephem(time, latitude, longitude, altitude=0, - pressure=101325, temperature=12, horizon='0:00'): - """ - Calculate the previous sunrise and sunset times using the PyEphem package. - - Parameters - ---------- - time : pandas.DatetimeIndex - Localized or UTC. - latitude : float - positive is north of 0 - longitude : float - positive is east of 0 - altitude : float, default 0 - distance above sea level in meters. - pressure : int or float, optional, default 101325 - air pressure in Pascals. - temperature : int or float, optional, default 12 - air temperature in degrees C. - horizon : string, format +/-X:YY - arc degrees:arc minutes from geometrical horizon for sunrise and - sunset, e.g., horizon='-0:34' when the sun's upper edge crosses the - geometrical horizon - - Returns - ------- - pandas.DataFrame - index is the same as input time.index - columns are 'sunrise' and 'sunset', times are localized to the - timezone time.tz - - See also - -------- - pyephem - """ - - try: - import ephem - except ImportError: - raise ImportError('PyEphem must be installed') - - # if localized, convert to UTC. otherwise, assume UTC. - try: - time_utc = time.tz_convert('UTC') - except TypeError: - time_utc = time - - obs, sun = _ephem_setup(latitude, longitude, altitude, - pressure, temperature, horizon) - # create lists of the next sunrise and sunset time localized to time.tz - previous_sunrise = [] - previous_sunset = [] - for thetime in time_utc: - obs.date = ephem.Date(thetime) - previous_sunrise.append(_ephem_to_timezone(obs.previous_rising(sun), - time.tz)) - previous_sunset.append(_ephem_to_timezone(obs.previous_setting(sun), - time.tz)) - - return pd.DataFrame(index=time, data={'sunrise': previous_sunrise, - 'sunset': previous_sunset}) - - def pyephem(time, latitude, longitude, altitude=0, pressure=101325, temperature=12, horizon='+0:00'): """ From 36bf3cc57d72e18462aaf447247dc1bcff40dc50 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Sun, 30 Sep 2018 13:16:20 -0600 Subject: [PATCH 25/33] Add transit, change golden to fixture, add horizon kwarg and error tests --- pvlib/solarposition.py | 41 ++++---- pvlib/test/test_solarposition.py | 156 ++++++++++++++++++++----------- 2 files changed, 127 insertions(+), 70 deletions(-) diff --git a/pvlib/solarposition.py b/pvlib/solarposition.py index f398ba83eb..f687dda838 100644 --- a/pvlib/solarposition.py +++ b/pvlib/solarposition.py @@ -6,6 +6,7 @@ # Rob Andrews (@Calama-Consulting), Calama Consulting, 2014 # Will Holmgren (@wholmgren), University of Arizona, 2014 # Tony Lorenzo (@alorenzo175), University of Arizona, 2015 +# Cliff hansen (@cwhanse), Sandia National Laboratories, 2018 from __future__ import division import os @@ -473,17 +474,17 @@ def _ephem_setup(latitude, longitude, altitude, pressure, temperature, return obs, sun -def get_rise_set_ephem(time, latitude, longitude, - next_or_previous='next', - altitude=0, - pressure=101325, temperature=12, horizon='0:00'): +def rise_set_transit_ephem(time, latitude, longitude, + next_or_previous='next', + altitude=0, + pressure=101325, temperature=12, horizon='0:00'): """ Calculate the next sunrise and sunset times using the PyEphem package. Parameters ---------- time : pandas.DatetimeIndex - Localized or UTC. + Must be localized latitude : float positive is north of 0 longitude : float @@ -498,15 +499,16 @@ def get_rise_set_ephem(time, latitude, longitude, air temperature in degrees C. horizon : string, format +/-X:YY arc degrees:arc minutes from geometrical horizon for sunrise and - sunset, e.g., horizon='-0:34' when the sun's upper edge crosses the + sunset, e.g., horizon='+0:00' to use sun center crossing the + geometrical horizon to define sunrise and sunset, + horizon='-0:34' for when the sun's upper edge crosses the geometrical horizon Returns ------- pandas.DataFrame - index is the same as input time.index - columns are 'sunrise' and 'sunset', times are localized to the - timezone time.tz + index is the same as input `time` argument + columns are 'sunrise', 'sunset', and 'transit' See also -------- @@ -518,11 +520,9 @@ def get_rise_set_ephem(time, latitude, longitude, except ImportError: raise ImportError('PyEphem must be installed') - # if localized, convert to UTC. otherwise, assume UTC. - try: - time_utc = time.tz_convert('UTC') - except TypeError: - time_utc = time + # times must be localized + if not time.tz: + raise ValueError('rise_set_ephem: times must be localized') obs, sun = _ephem_setup(latitude, longitude, altitude, pressure, temperature, horizon) @@ -539,7 +539,7 @@ def get_rise_set_ephem(time, latitude, longitude, sunrise = [] sunset = [] - for thetime in time_utc: + for thetime in time: obs.date = ephem.Date(thetime) sunrise.append(_ephem_to_timezone(rising(sun), time.tz)) sunset.append(_ephem_to_timezone(setting(sun), time.tz)) @@ -569,12 +569,15 @@ def pyephem(time, latitude, longitude, altitude=0, pressure=101325, air temperature in degrees C. horizon : string, optional, default '+0:00' arc degrees:arc minutes from geometrical horizon for sunrise and - sunset, e.g., horizon='-0:34' when the sun's upper edge crosses the + sunset, e.g., horizon='+0:00' to use sun center crossing the + geometrical horizon to define sunrise and sunset, + horizon='-0:34' for when the sun's upper edge crosses the geometrical horizon Returns ------- - DataFrame + pandas.DataFrame + index is the same as input `time` argument The DataFrame will have the following columns: apparent_elevation, elevation, apparent_azimuth, azimuth, @@ -840,7 +843,9 @@ def calc_time(lower_bound, upper_bound, latitude, longitude, attribute, value, Air temperature in degrees C. horizon : string, optional, default '+0:00' arc degrees:arc minutes from geometrical horizon for sunrise and - sunset, e.g., horizon='-0:34' when the sun's upper edge crosses the + sunset, e.g., horizon='+0:00' to use sun center crossing the + geometrical horizon to define sunrise and sunset, + horizon='-0:34' for when the sun's upper edge crosses the geometrical horizon xtol : float, optional, default 1.0e-12 The allowed error in the result from value diff --git a/pvlib/test/test_solarposition.py b/pvlib/test/test_solarposition.py index 27c516577e..e83916d93c 100644 --- a/pvlib/test/test_solarposition.py +++ b/pvlib/test/test_solarposition.py @@ -21,15 +21,21 @@ end=datetime.datetime(2014,6,26), freq='15Min') tus = Location(32.2, -111, 'US/Arizona', 700) # no DST issues possible -# In 2003, DST in US was from April 6 to October 26 -golden_mst = Location(39.742476, -105.1786, 'MST', 1830.14) # no DST issues possible -golden = Location(39.742476, -105.1786, 'America/Denver', 1830.14) # DST issues possible - times_localized = times.tz_localize(tus.tz) tol = 5 +@pytest.fixture() +def golden(): + return Location(39.742476, -105.1786, 'America/Denver', 1830.14) + + +@pytest.fixture() +def golden_mst(): + return Location(39.742476, -105.1786, 'MST', 1830.14) + + @pytest.fixture() def expected_solpos(): return pd.DataFrame({'elevation': 39.872046, @@ -101,7 +107,7 @@ def expected_rise_set_ephem(): # this doesn't mean that one code is better than the other. @requires_spa_c -def test_spa_c_physical(expected_solpos): +def test_spa_c_physical(expected_solpos, golden_mst): times = pd.date_range(datetime.datetime(2003,10,17,12,30,30), periods=1, freq='D', tz=golden_mst.tz) ephem_data = solarposition.spa_c(times, golden_mst.latitude, @@ -113,7 +119,7 @@ def test_spa_c_physical(expected_solpos): @requires_spa_c -def test_spa_c_physical_dst(expected_solpos): +def test_spa_c_physical_dst(expected_solpos, golden): times = pd.date_range(datetime.datetime(2003,10,17,13,30,30), periods=1, freq='D', tz=golden.tz) ephem_data = solarposition.spa_c(times, golden.latitude, @@ -123,7 +129,7 @@ def test_spa_c_physical_dst(expected_solpos): expected_solpos.index = times assert_frame_equal(expected_solpos, ephem_data[expected_solpos.columns]) -def test_spa_python_numpy_physical(expected_solpos): +def test_spa_python_numpy_physical(expected_solpos, golden): times = pd.date_range(datetime.datetime(2003,10,17,12,30,30), periods=1, freq='D', tz=golden_mst.tz) ephem_data = solarposition.spa_python(times, golden_mst.latitude, @@ -135,7 +141,7 @@ def test_spa_python_numpy_physical(expected_solpos): expected_solpos.index = times assert_frame_equal(expected_solpos, ephem_data[expected_solpos.columns]) -def test_spa_python_numpy_physical_dst(expected_solpos): +def test_spa_python_numpy_physical_dst(expected_solpos, golden): times = pd.date_range(datetime.datetime(2003,10,17,13,30,30), periods=1, freq='D', tz=golden.tz) ephem_data = solarposition.spa_python(times, golden.latitude, @@ -149,7 +155,7 @@ def test_spa_python_numpy_physical_dst(expected_solpos): @requires_numba -def test_spa_python_numba_physical(expected_solpos): +def test_spa_python_numba_physical(expected_solpos, golden_mst): times = pd.date_range(datetime.datetime(2003,10,17,12,30,30), periods=1, freq='D', tz=golden_mst.tz) ephem_data = solarposition.spa_python(times, golden_mst.latitude, @@ -163,7 +169,7 @@ def test_spa_python_numba_physical(expected_solpos): @requires_numba -def test_spa_python_numba_physical_dst(expected_solpos): +def test_spa_python_numba_physical_dst(expected_solpos, golden): times = pd.date_range(datetime.datetime(2003,10,17,13,30,30), periods=1, freq='D', tz=golden.tz) ephem_data = solarposition.spa_python(times, golden.latitude, @@ -176,7 +182,7 @@ def test_spa_python_numba_physical_dst(expected_solpos): @needs_pandas_0_17 -def test_get_sun_rise_set_transit(expected_rise_set_spa): +def test_get_sun_rise_set_transit(expected_rise_set_spa, golden): south = Location(-35.0, 0.0, tz='UTC') times = pd.DatetimeIndex([datetime.datetime(1996, 7, 5, 0), datetime.datetime(2004, 12, 4, 0)] @@ -216,9 +222,10 @@ def test_get_sun_rise_set_transit(expected_rise_set_spa): @requires_ephem -def test_get_rise_set_ephem(expected_rise_set_ephem): +def test_rise_set_transit_ephem(expected_rise_set_ephem, golden): # test for Golden, CO compare to USNO, using local midnight - result = solarposition.get_rise_set_ephem(expected_rise_set_ephem.index, + result = solarposition.rise_set_transit_ephem( + expected_rise_set_ephem.index, golden.latitude, golden.longitude, next_or_previous='next', @@ -230,7 +237,6 @@ def test_get_rise_set_ephem(expected_rise_set_ephem): result_rounded = pd.DataFrame(index=result.index) for col, data in result.iteritems(): result_rounded[col] = data.dt.round('min').tz_convert('MST') - del expected_rise_set_ephem['transit'] assert_frame_equal(expected_rise_set_ephem, result_rounded) # test next sunrise/sunset with times @@ -252,15 +258,20 @@ def test_get_rise_set_ephem(expected_rise_set_ephem): expected_rise_set_ephem.loc[datetime.datetime(2015, 1, 2), 'sunset'], expected_rise_set_ephem.loc[datetime.datetime(2015, 1, 2), 'sunset'], expected_rise_set_ephem.loc[datetime.datetime(2015, 1, 3), 'sunset']]) - - result = solarposition.get_rise_set_ephem(times, - golden.latitude, - golden.longitude, - next_or_previous='next', - altitude=golden.altitude, - pressure=0, - temperature=11, - horizon='-0:34') + expected['transit'] = pd.Series(index=times, data= + [expected_rise_set_ephem.loc[datetime.datetime(2015, 1, 2), 'transit'], + expected_rise_set_ephem.loc[datetime.datetime(2015, 1, 2), 'transit'], + expected_rise_set_ephem.loc[datetime.datetime(2015, 1, 3), 'transit'], + expected_rise_set_ephem.loc[datetime.datetime(2015, 1, 3), 'transit']]) + + result = solarposition.rise_set_transit_ephem(times, + golden.latitude, + golden.longitude, + next_or_previous='next', + altitude=golden.altitude, + pressure=0, + temperature=11, + horizon='-0:34') # round to nearest minute result_rounded = pd.DataFrame(index=result.index) for col, data in result.iteritems(): @@ -286,15 +297,20 @@ def test_get_rise_set_ephem(expected_rise_set_ephem): expected_rise_set_ephem.loc[datetime.datetime(2015, 1, 1), 'sunset'], expected_rise_set_ephem.loc[datetime.datetime(2015, 1, 2), 'sunset'], expected_rise_set_ephem.loc[datetime.datetime(2015, 1, 2), 'sunset']]) - - result = solarposition.get_rise_set_ephem(times, - golden.latitude, - golden.longitude, - next_or_previous='previous', - altitude=golden.altitude, - pressure=0, - temperature=11, - horizon='-0:34') + expected['transit'] = pd.Series(index=times, data= + [expected_rise_set_ephem.loc[datetime.datetime(2015, 1, 1), 'transit'], + expected_rise_set_ephem.loc[datetime.datetime(2015, 1, 1), 'transit'], + expected_rise_set_ephem.loc[datetime.datetime(2015, 1, 2), 'transit'], + expected_rise_set_ephem.loc[datetime.datetime(2015, 1, 3), 'transit']]) + + result = solarposition.rise_set_transit_ephem(times, + golden.latitude, + golden.longitude, + next_or_previous='previous', + altitude=golden.altitude, + pressure=0, + temperature=11, + horizon='-0:34') # round to nearest minute result_rounded = pd.DataFrame(index=result.index) for col, data in result.iteritems(): @@ -303,17 +319,17 @@ def test_get_rise_set_ephem(expected_rise_set_ephem): # test with different timezone times = times.tz_convert('UTC') - expected = expected.tz_convert('UTC') # resuse result from previous + expected = expected.tz_convert('UTC') # resuse result from previous for col, data in expected.iteritems(): expected[col] = data.dt.tz_convert('UTC') - result = solarposition.get_rise_set_ephem(times, - golden.latitude, - golden.longitude, - next_or_previous='previous', - altitude=golden.altitude, - pressure=0, - temperature=11, - horizon='-0:34') + result = solarposition.rise_set_transit_ephem(times, + golden.latitude, + golden.longitude, + next_or_previous='previous', + altitude=golden.altitude, + pressure=0, + temperature=11, + horizon='-0:34') # round to nearest minute result_rounded = pd.DataFrame(index=result.index) for col, data in result.iteritems(): @@ -322,7 +338,43 @@ def test_get_rise_set_ephem(expected_rise_set_ephem): @requires_ephem -def test_pyephem_physical(expected_solpos): +def test_rise_set_transit_ephem_error(expected_rise_set_ephem, golden): + with pytest.raises(ValueError): + solarposition.rise_set_transit_ephem(expected_rise_set_ephem.index, + golden.latitude, + golden.longitude, + next_or_previous='other') + tz_naive = pd.DatetimeIndex([datetime.datetime(2015, 1, 2, 3, 0, 0)]) + with pytest.raises(ValueError): + solarposition.rise_set_transit_ephem(tz_naive, + golden.latitude, + golden.longitude, + next_or_previous='next') + + +@requires_ephem +def test_rise_set_transit_ephem_horizon(golden): + times = pd.DatetimeIndex([datetime.datetime(2016, 1, 3, 0, 0, 0) + ]).tz_localize('MST') + # center of sun disk + center = solarposition.rise_set_transit_ephem(times, + latitude=golden.latitude, + longitude=golden.longitude) + edge = solarposition.rise_set_transit_ephem(times, + latitude=golden.latitude, + longitude=golden.longitude, + horizon='-0:34') + result_rounded = (edge['sunrise'] - center['sunrise']).dt.round('min') + + sunrise_delta = datetime.datetime(2016, 1, 3, 7, 17, 11) - \ + datetime.datetime(2016, 1, 3, 7, 21, 33) + expected = pd.Series(index=times, + data=sunrise_delta).dt.round('min') + assert_series_equal(expected, result_rounded) + + +@requires_ephem +def test_pyephem_physical(expected_solpos, golden_mst): times = pd.date_range(datetime.datetime(2003,10,17,12,30,30), periods=1, freq='D', tz=golden_mst.tz) ephem_data = solarposition.pyephem(times, golden_mst.latitude, @@ -334,7 +386,7 @@ def test_pyephem_physical(expected_solpos): @requires_ephem -def test_pyephem_physical_dst(expected_solpos): +def test_pyephem_physical_dst(expected_solpos, golden): times = pd.date_range(datetime.datetime(2003,10,17,13,30,30), periods=1, freq='D', tz=golden.tz) ephem_data = solarposition.pyephem(times, golden.latitude, @@ -380,7 +432,7 @@ def test_earthsun_distance(): assert_allclose(1, distance, atol=0.1) -def test_ephemeris_physical(expected_solpos): +def test_ephemeris_physical(expected_solpos, golden_mst): times = pd.date_range(datetime.datetime(2003,10,17,12,30,30), periods=1, freq='D', tz=golden_mst.tz) ephem_data = solarposition.ephemeris(times, golden_mst.latitude, @@ -393,7 +445,7 @@ def test_ephemeris_physical(expected_solpos): assert_frame_equal(expected_solpos, ephem_data[expected_solpos.columns]) -def test_ephemeris_physical_dst(expected_solpos): +def test_ephemeris_physical_dst(expected_solpos, golden): times = pd.date_range(datetime.datetime(2003,10,17,13,30,30), periods=1, freq='D', tz=golden.tz) ephem_data = solarposition.ephemeris(times, golden.latitude, @@ -405,7 +457,7 @@ def test_ephemeris_physical_dst(expected_solpos): assert_frame_equal(expected_solpos, ephem_data[expected_solpos.columns]) -def test_ephemeris_physical_no_tz(expected_solpos): +def test_ephemeris_physical_no_tz(expected_solpos, golden_mst): times = pd.date_range(datetime.datetime(2003,10,17,19,30,30), periods=1, freq='D') ephem_data = solarposition.ephemeris(times, golden_mst.latitude, @@ -418,7 +470,7 @@ def test_ephemeris_physical_no_tz(expected_solpos): assert_frame_equal(expected_solpos, ephem_data[expected_solpos.columns]) -def test_get_solarposition_error(): +def test_get_solarposition_error(golden): times = pd.date_range(datetime.datetime(2003,10,17,13,30,30), periods=1, freq='D', tz=golden.tz) with pytest.raises(ValueError): @@ -439,7 +491,7 @@ def test_get_solarposition_error(): 'equation_of_time', 'zenith'], index=expected_solpos().index)) ]) -def test_get_solarposition_pressure(pressure, expected): +def test_get_solarposition_pressure(pressure, expected, golden): times = pd.date_range(datetime.datetime(2003,10,17,13,30,30), periods=1, freq='D', tz=golden.tz) ephem_data = solarposition.get_solarposition(times, golden.latitude, @@ -463,7 +515,7 @@ def test_get_solarposition_pressure(pressure, expected): 'equation_of_time', 'zenith'], index=expected_solpos().index)) ]) -def test_get_solarposition_altitude(altitude, expected): +def test_get_solarposition_altitude(altitude, expected, golden): times = pd.date_range(datetime.datetime(2003,10,17,13,30,30), periods=1, freq='D', tz=golden.tz) ephem_data = solarposition.get_solarposition(times, golden.latitude, @@ -485,7 +537,7 @@ def test_get_solarposition_altitude(altitude, expected): ((None, 'nrel_numba', expected_solpos_multi())), (67.0, 'nrel_numba', expected_solpos_multi()) ]) -def test_get_solarposition_deltat(delta_t, method, expected): +def test_get_solarposition_deltat(delta_t, method, expected, golden): times = pd.date_range(datetime.datetime(2003,10,17,13,30,30), periods=2, freq='D', tz=golden.tz) ephem_data = solarposition.get_solarposition(times, golden.latitude, @@ -501,7 +553,7 @@ def test_get_solarposition_deltat(delta_t, method, expected): assert_frame_equal(this_expected, ephem_data[this_expected.columns]) -def test_get_solarposition_no_kwargs(expected_solpos): +def test_get_solarposition_no_kwargs(expected_solpos, golden): times = pd.date_range(datetime.datetime(2003,10,17,13,30,30), periods=1, freq='D', tz=golden.tz) ephem_data = solarposition.get_solarposition(times, golden.latitude, @@ -513,7 +565,7 @@ def test_get_solarposition_no_kwargs(expected_solpos): @requires_ephem -def test_get_solarposition_method_pyephem(expected_solpos): +def test_get_solarposition_method_pyephem(expected_solpos, golden): times = pd.date_range(datetime.datetime(2003, 10, 17, 13, 30, 30), periods=1, freq='D', tz=golden.tz) ephem_data = solarposition.get_solarposition(times, golden.latitude, From 39956cc9272b820c579bd7a73c98b9ef7f464e40 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Sun, 30 Sep 2018 13:20:57 -0600 Subject: [PATCH 26/33] style fixes --- docs/sphinx/source/api.rst | 2 +- docs/sphinx/source/whatsnew/v0.6.1.rst | 4 ++-- pvlib/test/test_solarposition.py | 17 +++++++---------- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/docs/sphinx/source/api.rst b/docs/sphinx/source/api.rst index ce28728517..c4da45ed3b 100644 --- a/docs/sphinx/source/api.rst +++ b/docs/sphinx/source/api.rst @@ -55,7 +55,7 @@ Additional functions for quantities closely related to solar position. solarposition.calc_time solarposition.pyephem_earthsun_distance solarposition.nrel_earthsun_distance - solarposition.get_rise_set_ephem + solarposition.rise_set_transit_ephem spa.calculate_deltat The spa module contains the implementation of the built-in NREL SPA diff --git a/docs/sphinx/source/whatsnew/v0.6.1.rst b/docs/sphinx/source/whatsnew/v0.6.1.rst index 8f0aefcc0d..0b6c94e3f0 100644 --- a/docs/sphinx/source/whatsnew/v0.6.1.rst +++ b/docs/sphinx/source/whatsnew/v0.6.1.rst @@ -12,11 +12,11 @@ date will require Python 3. (:issue:`501`) API Changes ~~~~~~~~~~~ - +* Added kwarg `horizon` to :func:`~pvlib.solarposition.pyephem` and :func:`~pvlib.solarposition.calc_time` with default value `'+0:00'` Enhancements ~~~~~~~~~~~~ -* :func:`~pvlib.solarposition.get_rise_set_ephem` returns sunrise and sunset times using pyephem +* :func:`~pvlib.solarposition.rise_set_transit_ephem` returns sunrise, sunset and transit times using pyephem Bug fixes diff --git a/pvlib/test/test_solarposition.py b/pvlib/test/test_solarposition.py index e83916d93c..a68c32d9bc 100644 --- a/pvlib/test/test_solarposition.py +++ b/pvlib/test/test_solarposition.py @@ -129,6 +129,7 @@ def test_spa_c_physical_dst(expected_solpos, golden): expected_solpos.index = times assert_frame_equal(expected_solpos, ephem_data[expected_solpos.columns]) + def test_spa_python_numpy_physical(expected_solpos, golden): times = pd.date_range(datetime.datetime(2003,10,17,12,30,30), periods=1, freq='D', tz=golden_mst.tz) @@ -141,6 +142,7 @@ def test_spa_python_numpy_physical(expected_solpos, golden): expected_solpos.index = times assert_frame_equal(expected_solpos, ephem_data[expected_solpos.columns]) + def test_spa_python_numpy_physical_dst(expected_solpos, golden): times = pd.date_range(datetime.datetime(2003,10,17,13,30,30), periods=1, freq='D', tz=golden.tz) @@ -225,14 +227,9 @@ def test_get_sun_rise_set_transit(expected_rise_set_spa, golden): def test_rise_set_transit_ephem(expected_rise_set_ephem, golden): # test for Golden, CO compare to USNO, using local midnight result = solarposition.rise_set_transit_ephem( - expected_rise_set_ephem.index, - golden.latitude, - golden.longitude, - next_or_previous='next', - altitude=golden.altitude, - pressure=0, - temperature=11, - horizon='-0:34') + expected_rise_set_ephem.index, golden.latitude, golden.longitude, + next_or_previous='next', altitude=golden.altitude, pressure=0, + temperature=11, horizon='-0:34') # round to nearest minute result_rounded = pd.DataFrame(index=result.index) for col, data in result.iteritems(): @@ -355,7 +352,7 @@ def test_rise_set_transit_ephem_error(expected_rise_set_ephem, golden): @requires_ephem def test_rise_set_transit_ephem_horizon(golden): times = pd.DatetimeIndex([datetime.datetime(2016, 1, 3, 0, 0, 0) - ]).tz_localize('MST') + ]).tz_localize('MST') # center of sun disk center = solarposition.rise_set_transit_ephem(times, latitude=golden.latitude, @@ -367,7 +364,7 @@ def test_rise_set_transit_ephem_horizon(golden): result_rounded = (edge['sunrise'] - center['sunrise']).dt.round('min') sunrise_delta = datetime.datetime(2016, 1, 3, 7, 17, 11) - \ - datetime.datetime(2016, 1, 3, 7, 21, 33) + datetime.datetime(2016, 1, 3, 7, 21, 33) expected = pd.Series(index=times, data=sunrise_delta).dt.round('min') assert_series_equal(expected, result_rounded) From f615f128d610048614f17ac0540368ef04aad790 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Sun, 30 Sep 2018 14:34:16 -0600 Subject: [PATCH 27/33] Fix references to fixture golden_mst, golden --- pvlib/test/test_location.py | 5 ++--- pvlib/test/test_solarposition.py | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/pvlib/test/test_location.py b/pvlib/test/test_location.py index b37a6cecc0..ef2b44c180 100644 --- a/pvlib/test/test_location.py +++ b/pvlib/test/test_location.py @@ -18,7 +18,7 @@ import pvlib from pvlib.location import Location -from test_solarposition import expected_solpos +from test_solarposition import expected_solpos, golden_mst from conftest import requires_scipy @@ -252,8 +252,7 @@ def test_from_tmy_2(): assert_frame_equal(loc.tmy_data, data) -def test_get_solarposition(expected_solpos): - from test_solarposition import golden_mst +def test_get_solarposition(expected_solpos, golden_mst): times = pd.date_range(datetime.datetime(2003,10,17,12,30,30), periods=1, freq='D', tz=golden_mst.tz) ephem_data = golden_mst.get_solarposition(times, temperature=11) diff --git a/pvlib/test/test_solarposition.py b/pvlib/test/test_solarposition.py index a68c32d9bc..87a80369b0 100644 --- a/pvlib/test/test_solarposition.py +++ b/pvlib/test/test_solarposition.py @@ -504,7 +504,7 @@ def test_get_solarposition_pressure(pressure, expected, golden): @pytest.mark.parametrize( "altitude, expected", [ - (golden.altitude, expected_solpos()), + (golden().altitude, expected_solpos()), (2000, pd.DataFrame( np.array([[ 39.88788, 50.11212, 194.34024, 39.87205, 14.64151, 50.12795]]), From 47c95c5301d956da8163a6f3bd1e3ccf6b3093eb Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Sun, 30 Sep 2018 15:17:12 -0600 Subject: [PATCH 28/33] Add transit output to rise_set_transit_ephem --- pvlib/solarposition.py | 7 ++++++- pvlib/test/test_solarposition.py | 10 +++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/pvlib/solarposition.py b/pvlib/solarposition.py index f687dda838..faaa2cfe17 100644 --- a/pvlib/solarposition.py +++ b/pvlib/solarposition.py @@ -530,22 +530,27 @@ def rise_set_transit_ephem(time, latitude, longitude, if next_or_previous.lower() == 'next': rising = obs.next_rising setting = obs.next_setting + transit = obs.next_transit elif next_or_previous.lower() == 'previous': rising = obs.previous_rising setting = obs.previous_setting + transit = obs.previous_transit else: raise ValueError("next_or_previous must be either 'next' or" + " 'previous'") sunrise = [] sunset = [] + transit = [] for thetime in time: obs.date = ephem.Date(thetime) sunrise.append(_ephem_to_timezone(rising(sun), time.tz)) sunset.append(_ephem_to_timezone(setting(sun), time.tz)) + transit.append(_ephem_to_timezone(transit(sun), time.tz)) return pd.DataFrame(index=time, data={'sunrise': sunrise, - 'sunset': sunset}) + 'sunset': sunset, + 'transit': transit}) def pyephem(time, latitude, longitude, altitude=0, pressure=101325, diff --git a/pvlib/test/test_solarposition.py b/pvlib/test/test_solarposition.py index 87a80369b0..db3c9ec9e6 100644 --- a/pvlib/test/test_solarposition.py +++ b/pvlib/test/test_solarposition.py @@ -130,7 +130,7 @@ def test_spa_c_physical_dst(expected_solpos, golden): assert_frame_equal(expected_solpos, ephem_data[expected_solpos.columns]) -def test_spa_python_numpy_physical(expected_solpos, golden): +def test_spa_python_numpy_physical(expected_solpos, golden_mst): times = pd.date_range(datetime.datetime(2003,10,17,12,30,30), periods=1, freq='D', tz=golden_mst.tz) ephem_data = solarposition.spa_python(times, golden_mst.latitude, @@ -503,13 +503,13 @@ def test_get_solarposition_pressure(pressure, expected, golden): @pytest.mark.parametrize( - "altitude, expected", [ - (golden().altitude, expected_solpos()), + "altitude, expected", + [(golden().altitude, expected_solpos()), (2000, pd.DataFrame( np.array([[ 39.88788, 50.11212, 194.34024, 39.87205, 14.64151, 50.12795]]), - columns=['apparent_elevation', 'apparent_zenith', 'azimuth', 'elevation', - 'equation_of_time', 'zenith'], + columns=['apparent_elevation', 'apparent_zenith', 'azimuth', + 'elevation', 'equation_of_time', 'zenith'], index=expected_solpos().index)) ]) def test_get_solarposition_altitude(altitude, expected, golden): From bef44f61b9cb2da48a0aaaddcc2fccc073c79eaa Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Sun, 30 Sep 2018 15:37:33 -0600 Subject: [PATCH 29/33] Fix transit conflict --- pvlib/solarposition.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pvlib/solarposition.py b/pvlib/solarposition.py index faaa2cfe17..f199311b52 100644 --- a/pvlib/solarposition.py +++ b/pvlib/solarposition.py @@ -541,16 +541,16 @@ def rise_set_transit_ephem(time, latitude, longitude, sunrise = [] sunset = [] - transit = [] + trans = [] for thetime in time: obs.date = ephem.Date(thetime) sunrise.append(_ephem_to_timezone(rising(sun), time.tz)) sunset.append(_ephem_to_timezone(setting(sun), time.tz)) - transit.append(_ephem_to_timezone(transit(sun), time.tz)) + trans.append(_ephem_to_timezone(transit(sun), time.tz)) return pd.DataFrame(index=time, data={'sunrise': sunrise, 'sunset': sunset, - 'transit': transit}) + 'transit': trans}) def pyephem(time, latitude, longitude, altitude=0, pressure=101325, From c9b5348d4b13ca082f631382265e1e85e168cf7a Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Sun, 30 Sep 2018 15:55:07 -0600 Subject: [PATCH 30/33] Fix column order --- pvlib/test/test_solarposition.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pvlib/test/test_solarposition.py b/pvlib/test/test_solarposition.py index db3c9ec9e6..a724f925a9 100644 --- a/pvlib/test/test_solarposition.py +++ b/pvlib/test/test_solarposition.py @@ -98,9 +98,9 @@ def expected_rise_set_ephem(): datetime.datetime(2015, 1, 3, 12, 5, 0), datetime.datetime(2015, 8, 2, 12, 7, 0) ]).tz_localize('MST').tolist() - return pd.DataFrame({'transit': transit, - 'sunrise': sunrise, - 'sunset': sunset}, + return pd.DataFrame({'sunrise': sunrise, + 'sunset': sunset, + 'transit': transit}, index=times) # the physical tests are run at the same time as the NREL SPA test. # pyephem reproduces the NREL result to 2 decimal places. From 92e0677ab4b5bf9da07d68a6d342e0748ea564fb Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Sun, 30 Sep 2018 16:20:19 -0600 Subject: [PATCH 31/33] Minor test adjustments --- pvlib/test/test_solarposition.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pvlib/test/test_solarposition.py b/pvlib/test/test_solarposition.py index a724f925a9..64d244cc29 100644 --- a/pvlib/test/test_solarposition.py +++ b/pvlib/test/test_solarposition.py @@ -91,7 +91,7 @@ def expected_rise_set_ephem(): sunset = pd.DatetimeIndex([datetime.datetime(2015, 1, 1, 16, 47, 0), datetime.datetime(2015, 1, 2, 16, 48, 0), datetime.datetime(2015, 1, 3, 16, 49, 0), - datetime.datetime(2015, 8, 2, 19, 13, 0) + datetime.datetime(2015, 8, 2, 19, 14, 0) ]).tz_localize('MST').tolist() transit = pd.DatetimeIndex([datetime.datetime(2015, 1, 1, 12, 4, 0), datetime.datetime(2015, 1, 2, 12, 5, 0), @@ -366,7 +366,8 @@ def test_rise_set_transit_ephem_horizon(golden): sunrise_delta = datetime.datetime(2016, 1, 3, 7, 17, 11) - \ datetime.datetime(2016, 1, 3, 7, 21, 33) expected = pd.Series(index=times, - data=sunrise_delta).dt.round('min') + data=sunrise_delta, + name='sunrise').dt.round('min') assert_series_equal(expected, result_rounded) From db08f8dbbef83a99835a355b8ff37567a961ec36 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Mon, 1 Oct 2018 08:39:46 -0600 Subject: [PATCH 32/33] Add handling of utcoffset --- pvlib/solarposition.py | 3 ++- pvlib/test/test_solarposition.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pvlib/solarposition.py b/pvlib/solarposition.py index f199311b52..c68c44f80c 100644 --- a/pvlib/solarposition.py +++ b/pvlib/solarposition.py @@ -543,7 +543,8 @@ def rise_set_transit_ephem(time, latitude, longitude, sunset = [] trans = [] for thetime in time: - obs.date = ephem.Date(thetime) + thetime = thetime.to_pydatetime() + obs.date = ephem.Date(thetime - thetime.utcoffset()) sunrise.append(_ephem_to_timezone(rising(sun), time.tz)) sunset.append(_ephem_to_timezone(setting(sun), time.tz)) trans.append(_ephem_to_timezone(transit(sun), time.tz)) diff --git a/pvlib/test/test_solarposition.py b/pvlib/test/test_solarposition.py index 64d244cc29..6c407a7a9b 100644 --- a/pvlib/test/test_solarposition.py +++ b/pvlib/test/test_solarposition.py @@ -91,7 +91,7 @@ def expected_rise_set_ephem(): sunset = pd.DatetimeIndex([datetime.datetime(2015, 1, 1, 16, 47, 0), datetime.datetime(2015, 1, 2, 16, 48, 0), datetime.datetime(2015, 1, 3, 16, 49, 0), - datetime.datetime(2015, 8, 2, 19, 14, 0) + datetime.datetime(2015, 8, 2, 19, 13, 0) ]).tz_localize('MST').tolist() transit = pd.DatetimeIndex([datetime.datetime(2015, 1, 1, 12, 4, 0), datetime.datetime(2015, 1, 2, 12, 5, 0), From c05bae7e4e1cfa93174589f3270c36a533b161b5 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Mon, 1 Oct 2018 08:51:53 -0600 Subject: [PATCH 33/33] Inline comment --- pvlib/solarposition.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pvlib/solarposition.py b/pvlib/solarposition.py index c68c44f80c..b8b2e20274 100644 --- a/pvlib/solarposition.py +++ b/pvlib/solarposition.py @@ -544,6 +544,8 @@ def rise_set_transit_ephem(time, latitude, longitude, trans = [] for thetime in time: thetime = thetime.to_pydatetime() + # pyephem drops timezone when converting to its internal datetime + # format, so handle timezone explicitly here obs.date = ephem.Date(thetime - thetime.utcoffset()) sunrise.append(_ephem_to_timezone(rising(sun), time.tz)) sunset.append(_ephem_to_timezone(setting(sun), time.tz))