Description
I am running initial tests having converted my Nautical Almanac software to use find_risings()
and find_settings()
when Skyfield VERSION is 1.48 or higher. Lower versions still use find_discrete()
. As both are implemented I began comparing them and I came across a discrepancy that I wish to describe in detail here.
To gain confidence in my 'quick and dirty' test program, I quote three test cases: the first two show correct results. I am aware that almanacs print time in UT (=UT1) and my test program simply prints UTC time, which is irrelevant here. Furthermore times in almanacs are rounded to minutes so a 24-hour search begins at 30 seconds before midnight. My test program begins at midnight (again irrelevant here).
TEST CASE 1: 31-May 2023 to 2-June 2023 at latitude 68°
I expect to see these results (my almanac data using find_discrete()
) for Wed Thu Fri:
The test Program prints results from find_risings()
and find_settings()
as 'moonrise' and 'moonset' and results from find_discrete() as 'moonevent' where '0' represents a moonset and '1' a moonrise:
The data looks good!
TEST CASE 2: 31-May 2023 to 2-June 2023 at latitude 72°
Again, the new routines match the data with the legacy find_discrete()
logic. Looks good!
TEST CASE 3: 18-Feb 2023 to 20-Feb 2023 at latitude 70°
Here I expected to see this:
but instead saw this:
Note that my old almanac code searches day-by-day forward for the next moonrise or moonset event to determine the current moon state. "All day above horizon" (a white rectangle) is printed because the next event detected is a moonset at 12:05 on Sunday. So what went wrong here? The Test Program shows this:
The "moonrise" at 11:35 on 19-Feb is 'False'. There is no moonrise on 19-Feb. I look into the official HMNAO Nautical Almanac and see a moonrise at 11:28 on Feb 19th. Exactly what find_discrete()
prints (when rounded). Why did find_risings()
miss it? I use a calculated horizon
value but if you omit the last parameter "-horizon" from find_risings()
, it makes no difference.
But the horizon value does have an impact on the results. Some reverse-engineering showed that increasing the value of "horizon" by 0.048 degrees not only fixes the problem but gives a result very close to 11:28, i.e. this works:
moonrise, yR = almanac.find_risings(observer, moon, t0, t1, -horizon-0.048)
Is find_risings()
using an incorrect horizon value?
Another thing that looks suspicious: on Feb 18th: both "False" events have the same time '2023-02-18 10:34:20Z'
.
I understand "False" events to mean that the moon vertical motion changes, even though this is below the horizon, i.e. not an actual moonrise or moonset. But I cannot believe that the moon changes direction twice within a split second.
I cannot dig deeper ....... I hope someone can look into this and either explain or fix the problem.
Here is my Test Program (note I use de421.bsp as my ephemeris):
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from datetime import date, datetime, timedelta
from math import atan, degrees
from skyfield import VERSION, almanac
from skyfield.api import Loader, Topos, wgs84, N, S, E, W
from skyfield.nutationlib import iau2000b
def getHorizon(t):
# calculate the angle of the moon below the horizon at moonrise/set
position = earth.at(t).observe(moon) # at noontime (for daily average distance)
distance = position.apparent().radec(epoch='date')[2]
dist_km = distance.km
sdm = degrees(atan(1737.4/dist_km)) # volumetric mean radius of moon = 1737.4 km
horizon = sdm + 0.5666667 # moon's equatorial radius + 34' (atmospheric refraction)
return horizon
def f_moon(topos, degBelowHorizon):
# Build a function of time that returns the moon state.
topos_at = (earth + topos).at
def is_moon_up_at(t):
"""The function that this returns will expect a single argument that is a
:class:`~skyfield.timelib.Time` and will return ``True`` if the moon is up,
else ``False``."""
t._nutation_angles = iau2000b(t.tt)
# Return `True` if the moon has risen by time `t`.
return topos_at(t).observe(moon).apparent().altaz()[0].degrees > -degBelowHorizon
is_moon_up_at.rough_period = 0.5 # twice a day
return is_moon_up_at
d = date(2023, 2, 18) # modify the starting date as required
lat = 70.0 # modify latitude as required (use float for Topos)
load = Loader("./")
ts = load.timescale()
eph = load('de421.bsp')
earth = eph['earth']
moon = eph['moon']
dt = datetime(d.year, d.month, d.day, 0, 0, 0)
print("Skyfield version = {}; Results for latitude {} ...".format(VERSION,lat))
for i in range(3): # sample 3 successive dates
t0 = ts.ut1(dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second)
t0noon = ts.ut1(dt.year, dt.month, dt.day, dt.hour+12, dt.minute, dt.second)
t1 = ts.ut1(dt.year, dt.month, dt.day+1, dt.hour, dt.minute, dt.second)
topos = wgs84.latlon(lat, 0.0 * E, elevation_m=0.0)
observer = earth + topos
horizon = getHorizon(t0noon)
moonrise, yR = almanac.find_risings(observer, moon, t0, t1, -horizon) # -horizon-0.048 fixes problem
moonset, yS = almanac.find_settings(observer, moon, t0, t1, -horizon)
locn = Topos(lat, "0.0 E")
moonevent, y = almanac.find_discrete(t0, t1, f_moon(locn, horizon))
print("{}: {} x moonrise, {} x moonset; {} x moonevent; horizon = {:.4f}°".format(dt.strftime("%Y-%m-%d"),len(yR),len(yS),len(y),horizon))
print(" moonrise ",moonrise.utc_iso(' ')," ",yR)
print(" moonset ",moonset.utc_iso(' ') ," ",yS)
print(" moonevent",moonevent.utc_iso(' ')," ",y)
dt += timedelta(days=1)
Sincere thanks to whoever looks into this case. I hope I provided a useful test case here.