-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
sun_rise_set_transit_spa
behavior changed in v0.11.1
#2238
Comments
Huh. I agree that v0.11.1 isn't the desired behavior, nor was a change intended. Now I'm curious about two things:
Before
After
For the second question, it is because |
In other words: those two snippets both "truncate" the datetimes to midnight (via |
However we fix it, let's add some inline comments. |
Sorry for the unexpected change. In fact, #2055 managed to fix an ambiguous issue of Thank you for raising this issue. Please let me clarify the issues one by one. I'll begin by taking the original output of import datetime
import pandas as pd
import numpy as np
import pvlib
latitude = 40
longitude = -80
tz = "Etc/GMT+5"
times = pd.date_range("1999-12-31 18:00", "2000-01-01 19:00", freq = 'H', tz=tz)[[0,1,5,6,13,14,-2,-1]]
times_utc = times.tz_convert('UTC')
utcoffset_hours = times[0].utcoffset().total_seconds() / 3600 sunrise_spa_c_local = pvlib.solarposition.spa_c(times, latitude, longitude, raw_spa_output=True)[['sunrise']]
sunrise_spa_c_utc = pvlib.solarposition.spa_c(times_utc, latitude, longitude, raw_spa_output=True)[['sunrise']]
sunrise_spa_c = sunrise_spa_c_local.join(
sunrise_spa_c_utc.tz_convert(tz), lsuffix='_spa_c_local', rsuffix='_spa_c_utc', how='outer')
assert np.array_equal(sunrise_spa_c_local, sunrise_spa_c_utc)
sunrise_spa_c.head(2) This shows the sunrise hour returned from
The output of sunrise_spa_c_local_fixed = sunrise_spa_c_local + utcoffset_hours
sunrise_spa_c_fixed = sunrise_spa_c.copy()
sunrise_spa_c_fixed['sunrise_spa_c_local'] = sunrise_spa_c_local_fixed
sunrise_spa_c_fixed.rename(columns={'sunrise_spa_c_local':'sunrise_spa_c_fixed_local'}, inplace=True)
sunrise_spa_c_local_datetime = sunrise_spa_c_local_fixed.apply(
lambda row: row.apply(lambda x: row.name.normalize() + datetime.timedelta(hours=x)), axis=1)
sunrise_spa_c_utc_datetime = sunrise_spa_c_utc.apply(
lambda row: row.apply(lambda x: row.name.normalize() + datetime.timedelta(hours=x)), axis=1)
sunrise_spa_c_datetime = sunrise_spa_c_local_datetime.join(
sunrise_spa_c_utc_datetime.tz_convert(tz), lsuffix='_spa_c_fixed_local', rsuffix='_spa_c_utc', how='outer')
assert np.array_equal(sunrise_spa_c_local_fixed - utcoffset_hours, sunrise_spa_c_utc)
assert not np.array_equal(
sunrise_spa_c_local_datetime.applymap(lambda x: x.date()),
sunrise_spa_c_utc_datetime.applymap(lambda x: x.date()))
sunrise_spa_c_fixed.join(
sunrise_spa_c_datetime, lsuffix='_original_hour', rsuffix='_derived_datetime', how='outer') There are two possible approaches to adjust #2055 in sunrise_spa_py_local = pvlib.solarposition.sun_rise_set_transit_spa(times, latitude, longitude)[['sunrise']]
sunrise_spa_py_utc = pvlib.solarposition.sun_rise_set_transit_spa(times_utc, latitude, longitude)[['sunrise']]
sunrise_spa_py = sunrise_spa_py_local.join(
sunrise_spa_py_utc.tz_convert(tz), lsuffix='_spa_py_local', rsuffix='_spa_py_utc')
sunrise_spa_py_hour = sunrise_spa_py.applymap(lambda x: x.hour + x.minute / 60 + x.second / 3600)
assert np.array_equal(sunrise_spa_py_local, sunrise_spa_py_utc)
sunrise_spa_py_hour.join(sunrise_spa_py, lsuffix='_derived_hour_v0.11.1', rsuffix='_original_datetime_v0.11.1') While the primary concern with this issue is that the change was unexpected, it successfully resolved the ambiguity of having two sunrises on the same local date. On the other hand, eot = pvlib.solarposition.equation_of_time_spencer71(
times_utc.dayofyear) # minutes
decl = pvlib.solarposition.declination_spencer71(times_utc.dayofyear) # radians
sunrise_geometric_local = pvlib.solarposition.sun_rise_set_transit_geometric(
times, latitude, longitude, decl, eot)[0].to_series(index=times, name='sunrise').to_frame()
sunrise_geometric_utc = pvlib.solarposition.sun_rise_set_transit_geometric(
times_utc, latitude, longitude, decl, eot)[0].to_series(index=times_utc, name='sunrise').to_frame()
sunrise_geometric = sunrise_geometric_local.join(
sunrise_geometric_utc.tz_convert(tz), lsuffix='_geometric_local', rsuffix='_geometric_utc')
sunrise_geometric_hour = sunrise_geometric.applymap(lambda x: x.hour + x.minute / 60 + x.second / 3600)
assert np.isclose(
sunrise_geometric_hour['sunrise_geometric_local'] - utcoffset_hours,
sunrise_geometric_hour['sunrise_geometric_utc']).all()
assert not np.array_equal(
sunrise_geometric_local.applymap(lambda x: x.date()), sunrise_geometric_utc.applymap(lambda x: x.date()))
sunrise_geometric_hour.join(sunrise_geometric, lsuffix='_derived_hour', rsuffix='_original_datetime') Although neither of these two approaches alters the original pattern of sunrise hours from sunrise_spa_py_local = pvlib.solarposition.sun_rise_set_transit_spa(times, latitude, longitude)[['sunrise']]
sunrise_spa_py_utc = pvlib.solarposition.sun_rise_set_transit_spa(times_utc, latitude, longitude)[['sunrise']]
sunrise_spa_py = sunrise_spa_py_local.join(
sunrise_spa_py_utc.tz_convert(tz), lsuffix='_spa_py_local', rsuffix='_spa_py_utc', how='outer')
sunrise_spa_py_hour = sunrise_spa_py.applymap(lambda x: x.hour + x.minute / 60 + x.second / 3600)
assert not np.array_equal(
sunrise_spa_py_hour['sunrise_spa_py_local'] - utcoffset_hours, sunrise_spa_py_hour['sunrise_spa_py_utc'])
assert not np.array_equal(
sunrise_spa_py_local.applymap(lambda x: x.date()), sunrise_spa_py_utc.applymap(lambda x: x.date()))
sunrise_spa_py_hour.join(
sunrise_spa_py, lsuffix='_derived_hour_v0.11.0', rsuffix='_original_datetime_v0.11.0', how='outer') Unlike the other two approaches, which have consistent times and only change at UTC midnights even for local hours, the behavior of assert abs(7.694722 + 5 - 12.697222) > 1e-3
assert abs(7.697222 + 5 - 12.699444) > 1e-3 On the contrary, assert abs(12.694794 - 12.694722) <= 1e-3
assert abs(12.697481 - 12.697222) <= 1e-3
assert abs(12.699564 - 12.699444) <= 1e-3 PyEphem, on the other hand, takes a different approach. It seems closest to the current behavior of sunrise_ephem_local = pvlib.solarposition.sun_rise_set_transit_ephem(times, latitude, longitude)[['sunrise']]
sunrise_ephem_utc = pvlib.solarposition.sun_rise_set_transit_ephem(times_utc, latitude, longitude)[['sunrise']]
sunrise_ephem = sunrise_ephem_local.join(
sunrise_ephem_utc.tz_convert(tz), lsuffix='_ephem_local', rsuffix='_ephem_utc')
sunrise_ephem_hour = sunrise_ephem.applymap(lambda x: x.hour + x.minute / 60 + x.second / 3600)
assert np.array_equal(sunrise_ephem_local, sunrise_ephem_utc)
sunrise_ephem_hour.join(sunrise_ephem, lsuffix='_derived_hour', rsuffix='_original_datetime') To sum up, I've identified four possible approaches to resolve this issue. One option is to modify Thank you for reading! Which option do you think is best? |
Describe the bug
With #2055,
pvlib.solarposition.sun_rise_set_transit_spa
sunrise etc times are in the next/previous day, depending on the time zone of the input timestamps and how close to midnight the timestamps are.To Reproduce
Output:
v0.11.0:
v0.11.1:
In v0.11.1, the dates are based on the UTC equivalent of the input timestamps. Previously, the dates were taken directly from the input timestamps.
Expected behavior
The previous behavior is more intuitive to me. If I am calculating sunrise/sunset, I am likely working in local time, and wanting to calculate sunrise/sunset accordingly.
Unlike
dayofyear
-based calculations for Earth's orbital position (the focus of #2055), I think sunrise/sunset calculations should respect the timezone of the input timestamps. I think we should revert to the previous behavior.Additional context
#1631 is somewhat similar, although for a different underlying cause and calculation method.
The text was updated successfully, but these errors were encountered: