Switch to keeping only one whole number in a Time

Instead of splitting every time scale into two floats, let’s keep only a
single master `whole` number and then a series of fractions, each that
when combined with the whole gives the time in a particular scale.  This
saves memory, keeps the attribute list smaller, is conceptually simpler,
and avoids the problem I just encountered with wanting a high precision
two-float version of `ut1` — `ut1_1` or `ut11` would look confusing to
others reading the code, and given that the UT1 timescale at least
includes a number, folks reading `tt1` and `tt2` might have thought
those were different timescales instead of simply two parts of a single
number.  So I will be using `whole` and `fraction` consistently in the
code from now on.
This commit is contained in:
Brandon Rhodes 2020-05-12 10:09:52 -04:00
parent c2c32455a8
commit 1f8e66a42f
4 changed files with 28 additions and 25 deletions

View File

@ -69,8 +69,11 @@ def main():
def reduce_precision(t):
# The NOVAS library uses only 64-bit precision for Julian dates.
# Some of these tests are so sensitive they can see the difference!
t.tdb1 = t.tdb1 + t.tdb2
t.tdb2 = 0.0
# So we need to collapse the Julian dates back into single floats.
delta = t.tdb - t.tt
t.whole = t.tdb
t.tt_fraction = delta
t.tdb_fraction = 0.0
""")

View File

@ -209,7 +209,7 @@ class ChebyshevPosition(SPICESegment):
segment = self.spk_segment
try:
position, velocity = segment.compute_and_differentiate(
t.tdb1, t.tdb2)
t.whole, t.tdb_fraction)
except OutOfRangeError as e:
start_time = t.ts.tdb(jd=segment.start_jd)
end_time = t.ts.tdb(jd=segment.end_jd)
@ -226,7 +226,7 @@ class ChebyshevPosition(SPICESegment):
class ChebyshevPositionVelocity(SPICESegment):
def _at(self, t):
pv = self.spk_segment.compute(t.tdb1, t.tdb2)
pv = self.spk_segment.compute(t.whole, t.tdb_fraction)
return pv[:3] / AU_KM, pv[3:] * DAY_S / AU_KM, None, None

View File

@ -35,8 +35,11 @@ def earth():
def reduce_precision(t):
# The NOVAS library uses only 64-bit precision for Julian dates.
# Some of these tests are so sensitive they can see the difference!
t.tdb1 = t.tdb1 + t.tdb2
t.tdb2 = 0.0
# So we need to collapse the Julian dates back into single floats.
delta = t.tdb - t.tt
t.whole = t.tdb
t.tt_fraction = delta
t.tdb_fraction = 0.0
def test_calendar_date_0():
compare(timelib.calendar_date(2440423.345833333), array((1969, 7, 20.345833333209157)), 0.0)

View File

@ -221,9 +221,7 @@ class Timescale(object):
_to_array(hour), _to_array(minute), _to_array(second),
)
tdb = _to_array(tdb)
tt = tdb - tdb_minus_tt(tdb) / DAY_S
t = Time(self, tt)
t.tdb1, t.tdb2 = tdb, 0.0
t = Time(self, tdb, - tdb_minus_tt(tdb) / DAY_S)
return t
def tdb_jd(self, jd):
@ -237,9 +235,7 @@ class Timescale(object):
"""
tdb = _to_array(jd)
tt = tdb - tdb_minus_tt(tdb) / DAY_S
t = Time(self, tt)
t.tdb1, t.tdb2 = tdb, 0.0
t = Time(self, tdb, - tdb_minus_tt(tdb) / DAY_S)
return t
def ut1(self, year=None, month=1, day=1, hour=0, minute=0, second=0.0,
@ -315,11 +311,10 @@ class Time(object):
psi_correction = 0.0
eps_correction = 0.0
def __init__(self, ts, tt, tt2=0.0):
def __init__(self, ts, tt, tt_fraction=0.0):
self.ts = ts
self.tt1, fraction = divmod(tt, 1.0)
self.tt2 = fraction + tt2
self.tdb1 = self.tt1
self.whole, fraction = divmod(tt, 1.0)
self.tt_fraction = fraction + tt_fraction
self.shape = getattr(tt, 'shape', ())
def __len__(self):
@ -339,8 +334,8 @@ class Time(object):
# TODO: also copy cached matrices?
# TODO: raise non-IndexError exception if this Time is not an array;
# otherwise, a `for` loop over it will not raise an error.
t = Time(self.ts, self.tt1[index], self.tt2[index])
for name in 'tai', 'tdb1', 'tdb2', 'ut1', 'delta_t':
t = Time(self.ts, self.whole[index], self.tt_fraction[index])
for name in 'tai', 'tdb_fraction', 'ut1', 'delta_t':
value = getattr(self, name, None)
if value is not None:
if getattr(value, 'shape', None):
@ -569,8 +564,9 @@ class Time(object):
i = searchsorted(ts._leap_reverse_dates, tai, 'right')
j = tai - ts.leap_offsets[i] / DAY_S
whole1, fraction = divmod(self.tt1, 1.0)
whole2, fraction = divmod(offset - tt_minus_tai + fraction + self.tt2
whole1, fraction = divmod(self.whole, 1.0)
whole2, fraction = divmod(offset - tt_minus_tai
+ fraction + self.tt_fraction
- ts.leap_offsets[i] / DAY_S + 0.5, 1.0)
whole = (whole1 + whole2).astype(int)
@ -650,9 +646,9 @@ class Time(object):
return array(utc) if self.shape else utc
@reify
def tdb2(self):
tt2 = self.tt2
return tt2 + tdb_minus_tt(self.tt1 + tt2) / DAY_S
def tdb_fraction(self):
fraction = self.tt_fraction
return fraction + tdb_minus_tt(self.whole + fraction) / DAY_S
@reify
def ut1(self):
@ -665,6 +661,7 @@ class Time(object):
@reify
def dut1(self):
# TODO: add test, and then streamline this computation.
return (self.tt - self._utc_float()) * DAY_S - self.delta_t
@reify
@ -687,11 +684,11 @@ class Time(object):
@property
def tt(self):
return self.tt1 + self.tt2
return self.whole + self.tt_fraction
@property
def tdb(self):
return self.tdb1 + self.tdb2
return self.whole + self.tdb_fraction
# Various dunders.