Skip to content

Commit b492c01

Browse files
author
Duncan Macleod
committed
Merge branch 'typing' into 'main'
Refactor package into modules and add typing Closes #26 See merge request gwpy/ligotimegps!27
2 parents 0d4b605 + fbb358f commit b492c01

File tree

4 files changed

+577
-345
lines changed

4 files changed

+577
-345
lines changed

ligotimegps/__init__.py

Lines changed: 8 additions & 318 deletions
Original file line numberDiff line numberDiff line change
@@ -16,331 +16,21 @@
1616
# You should have received a copy of the GNU General Public License
1717
# along with ligotimegps. If not, see <http://www.gnu.org/licenses/>.
1818

19-
"""`ligotimegps` provides a pure-python version of the |lal.LIGOTimeGPS|_
20-
object, used to represent GPS times (number of seconds elapsed since GPS
21-
epoch) with nanoseconds precision.
19+
"""A pure-python version of the |lal.LIGOTimeGPS|_ object.
20+
21+
`~ligotimegps.LIGOTimeGPS` is used to represent GPS times
22+
(number of seconds elapsed since the GPS epoch) with nanosecond precision.
2223
2324
The code provided here is much slower than the C-implementation provided
2425
by LAL, so if you really care about performance, don't use this module.
2526
"""
2627

27-
from math import (modf, log, isinf, log2)
28-
from functools import total_ordering
29-
from decimal import Decimal
28+
from .ligotimegps import LIGOTimeGPS
29+
from .protocol import LIGOTimeGPSLike
3030

3131
try:
3232
from ._version import version as __version__
33-
except ModuleNotFoundError: # development mode
33+
except ModuleNotFoundError: # pragma: no cover
3434
__version__ = ""
3535

36-
__all__ = ["LIGOTimeGPS"]
37-
38-
39-
@total_ordering
40-
class LIGOTimeGPS:
41-
"""An object for storing times with nanosecond resolution.
42-
43-
Internally the time is represented as a signed integer `gpsSeconds` part
44-
and an unsigned integer `gpsNanoseconds` part.
45-
The actual time is always constructed by adding the nanoseconds to the
46-
seconds.
47-
So -0.5 s is represented by setting seconds = -1, and nanoseconds to
48-
500000000.
49-
50-
Parameters
51-
----------
52-
seconds : `int`, `str`
53-
the count of seconds
54-
55-
nanoseconds : `int`, `str`, optional
56-
the count of nanoseconds
57-
58-
Examples
59-
--------
60-
>>> LIGOTimeGPS(100.5)
61-
LIGOTimeGPS(100, 500000000)
62-
>>> LIGOTimeGPS("100.5")
63-
LIGOTimeGPS(100, 500000000)
64-
>>> LIGOTimeGPS(100, 500000000)
65-
LIGOTimeGPS(100, 500000000)
66-
>>> LIGOTimeGPS(0, 100500000000)
67-
LIGOTimeGPS(100, 500000000)
68-
>>> LIGOTimeGPS(100.2, 300000000)
69-
LIGOTimeGPS(100, 500000000)
70-
>>> LIGOTimeGPS("0.000000001")
71-
LIGOTimeGPS(0, 1)
72-
>>> LIGOTimeGPS("0.0000000012")
73-
LIGOTimeGPS(0, 1)
74-
>>> LIGOTimeGPS("0.0000000018")
75-
LIGOTimeGPS(0, 2)
76-
>>> LIGOTimeGPS("-0.8")
77-
LIGOTimeGPS(-1, 200000000)
78-
>>> LIGOTimeGPS("-1.2")
79-
LIGOTimeGPS(-2, 800000000)
80-
"""
81-
82-
def __init__(self, seconds, nanoseconds=0):
83-
"""Create a LIGOTimeGPS instance."""
84-
if not isinstance(nanoseconds, (float, int)):
85-
nanoseconds = float(nanoseconds)
86-
if isinstance(seconds, float):
87-
ns, seconds = modf(seconds)
88-
seconds = int(seconds)
89-
nanoseconds += ns * 1e9
90-
elif not isinstance(seconds, int):
91-
if isinstance(seconds, (str, bytes)):
92-
try:
93-
seconds = str(Decimal(seconds))
94-
except ArithmeticError:
95-
msg = f"invalid literal for {type(self).__name__}: {seconds}"
96-
raise TypeError(msg)
97-
sign = -1 if seconds.startswith("-") else +1
98-
if "." in seconds:
99-
seconds, ns = seconds.split(".")
100-
ns = sign * int(ns.ljust(9, "0"))
101-
else:
102-
ns = 0
103-
seconds = int(seconds)
104-
nanoseconds += ns
105-
elif (hasattr(seconds, "gpsSeconds") and
106-
hasattr(seconds, "gpsNanoSeconds")): # lal.LIGOTimeGPS
107-
nanoseconds += seconds.gpsNanoSeconds
108-
seconds = seconds.gpsSeconds
109-
else:
110-
msg = (
111-
f"cannot convert {seconds!r} ({seconds.__class__.__name__})"
112-
f" to {type(self).__name__}"
113-
)
114-
raise TypeError(msg)
115-
self._seconds = seconds + int(nanoseconds // 1000000000)
116-
self._nanoseconds = int(nanoseconds % 1000000000)
117-
118-
# define read-only properties to access each part
119-
seconds = gpsSeconds = property(
120-
fget=lambda self: self._seconds,
121-
doc="Seconds since 0h UTC 6 Jan 1980",
122-
)
123-
nanoseconds = gpsNanoSeconds = property(
124-
fget=lambda self: self._nanoseconds,
125-
doc="residual nanoseconds",
126-
)
127-
128-
# -- representations ------------------------
129-
130-
def __repr__(self):
131-
return "LIGOTimeGPS(%d, %u)" % (self._seconds, self._nanoseconds)
132-
133-
def __str__(self):
134-
"""Return an ASCII string representation of a `LIGOTimeGPS`."""
135-
if (self._seconds >= 0) or (self._nanoseconds == 0):
136-
s = "%d.%09u" % (self._seconds, self._nanoseconds)
137-
elif self._seconds < -1:
138-
s = "%d.%09u" % (self._seconds + 1, 1000000000 - self._nanoseconds)
139-
else:
140-
s = "-0.%09u" % (1000000000 - self._nanoseconds)
141-
return s.rstrip("0").rstrip(".")
142-
143-
def __float__(self):
144-
"""Convert a `LIGOTimeGPS` to seconds as a float.
145-
146-
Examples
147-
--------
148-
>>> float(LIGOTimeGPS(100.5))
149-
100.5
150-
"""
151-
return self._seconds + self._nanoseconds * 1e-9
152-
153-
def __int__(self):
154-
"""Return the integer part (seconds) of a `LIGOTimeGPS` as an int.
155-
156-
Examples
157-
--------
158-
>>> int(LIGOTimeGPS(100.5))
159-
100
160-
"""
161-
return self._seconds
162-
163-
def ns(self):
164-
"""Convert a `LIGOTimeGPS` to a count of nanoseconds as an int.
165-
166-
When running python2.7 on Windows this is returned as `numpy.long`
167-
to guarantee long-ness.
168-
169-
Examples
170-
--------
171-
>>> LIGOTimeGPS(100.5).ns()
172-
100500000000
173-
"""
174-
return self._seconds * 1000000000 + self._nanoseconds
175-
176-
# -- comparison -----------------------------
177-
178-
def __eq__(self, other):
179-
if isinf(other):
180-
return False
181-
if not isinstance(other, LIGOTimeGPS):
182-
other = LIGOTimeGPS(other)
183-
return (self._seconds == other._seconds and
184-
self._nanoseconds == other._nanoseconds)
185-
186-
def __ne__(self, other):
187-
return not (self == other)
188-
189-
def __lt__(self, other):
190-
if isinf(other) and other > 0: # +infinity
191-
return True
192-
if isinf(other): # -infinity
193-
return False
194-
if not isinstance(other, LIGOTimeGPS):
195-
other = LIGOTimeGPS(other)
196-
if self._seconds < other._seconds or (self._seconds == other._seconds and
197-
self._nanoseconds < other._nanoseconds):
198-
return True
199-
return False
200-
201-
def __hash__(self):
202-
return self._seconds ^ self._nanoseconds
203-
204-
def __bool__(self):
205-
"""Return True if the `LIGOTimeGPS` is nonzero.
206-
207-
Examples
208-
--------
209-
>>> bool(LIGOTimeGPS(100.5))
210-
True
211-
"""
212-
return bool(self._seconds or self._nanoseconds)
213-
214-
# -- arithmetic -----------------------------
215-
216-
def __round__(self, n=0):
217-
if n == 0 and self.nanoseconds >= 5e8:
218-
return type(self)(self._seconds+1)
219-
if n == 0:
220-
return type(self)(self._seconds)
221-
return type(self)(self._seconds, round(self._nanoseconds, -9 + n))
222-
223-
def __add__(self, other):
224-
"""Add a value to a `LIGOTimeGPS`.
225-
226-
If the value being added to the `LIGOTimeGPS` is not also a
227-
`LIGOTimeGPS`, then an attempt is made to convert it to `LIGOTimeGPS`.
228-
229-
Examples
230-
--------
231-
>>> LIGOTimeGPS(100.5) + LIGOTimeGPS(3)
232-
LIGOTimeGPS(103, 500000000)
233-
>>> LIGOTimeGPS(100.5) + 3
234-
LIGOTimeGPS(103, 500000000)
235-
>>> LIGOTimeGPS(100.5) + "3"
236-
LIGOTimeGPS(103, 500000000)
237-
"""
238-
if not isinstance(other, LIGOTimeGPS):
239-
other = LIGOTimeGPS(other)
240-
return LIGOTimeGPS(self._seconds + other._seconds,
241-
self._nanoseconds + other._nanoseconds)
242-
243-
# addition is commutative.
244-
__radd__ = __add__
245-
246-
def __sub__(self, other):
247-
"""Subtract a value from a `LIGOTimeGPS`.
248-
249-
If the value being subtracted from the `LIGOTimeGPS` is not also
250-
a `LIGOTimeGPS`, then an attempt is made to convert it to a
251-
`LIGOTimeGPS`.
252-
253-
Examples
254-
--------
255-
>>> LIGOTimeGPS(100.5) - LIGOTimeGPS(3)
256-
LIGOTimeGPS(97, 500000000)
257-
>>> LIGOTimeGPS(100.5) - 3
258-
LIGOTimeGPS(97, 500000000)
259-
>>> LIGOTimeGPS(100.5) - "3"
260-
LIGOTimeGPS(97, 500000000)
261-
"""
262-
if not isinstance(other, LIGOTimeGPS):
263-
other = LIGOTimeGPS(other)
264-
return LIGOTimeGPS(self._seconds - other._seconds,
265-
self._nanoseconds - other._nanoseconds)
266-
267-
def __rsub__(self, other):
268-
"""Subtract a `LIGOTimeGPS` from a value."""
269-
if not isinstance(other, LIGOTimeGPS):
270-
other = LIGOTimeGPS(other)
271-
return LIGOTimeGPS(other._seconds - self._seconds,
272-
other._nanoseconds - self._nanoseconds)
273-
274-
def __mul__(self, other):
275-
"""Multiply a `LIGOTimeGPS` by a number.
276-
277-
Examples
278-
--------
279-
>>> LIGOTimeGPS(100.5) * 2
280-
LIGOTimeGPS(201, 0)
281-
"""
282-
seconds = self._seconds
283-
nanoseconds = self._nanoseconds
284-
285-
if seconds < 0 and nanoseconds > 0:
286-
seconds += 1
287-
nanoseconds -= 1000000000
288-
289-
slo = seconds % 131072
290-
shi = seconds - slo
291-
olo = other % 2**(int(log2(other)) - 26) if other else 0
292-
ohi = other - olo
293-
294-
nanoseconds *= float(other)
295-
seconds = 0.
296-
for addend in (slo * olo, shi * olo, slo * ohi, shi * ohi):
297-
n, s = modf(addend)
298-
seconds += s
299-
nanoseconds += n * 1e9
300-
301-
return LIGOTimeGPS(seconds, round(nanoseconds))
302-
303-
# multiplication is commutative
304-
__rmul__ = __mul__
305-
306-
def __truediv__(self, other):
307-
"""Divide a `LIGOTimeGPS` by a number.
308-
309-
Examples
310-
--------
311-
>>> LIGOTimeGPS(100.5) / 2
312-
LIGOTimeGPS(50, 250000000)
313-
"""
314-
quotient = LIGOTimeGPS(float(self) / float(other))
315-
for n in range(100):
316-
residual = float(self - quotient * other) / float(other)
317-
quotient += residual
318-
if abs(residual) <= 0.5e-9:
319-
break
320-
return quotient
321-
322-
__div__ = __truediv__
323-
324-
def __mod__(self, other):
325-
"""Compute the remainder when a `LIGOTimeGPS` is divided by a number.
326-
327-
Examples
328-
--------
329-
>>> LIGOTimeGPS(100.5) % 3
330-
LIGOTimeGPS(1, 500000000)
331-
"""
332-
quotient = int(self / other)
333-
return self - quotient * other
334-
335-
# -- unary arithmetic -----------------------
336-
337-
def __pos__(self):
338-
return self
339-
340-
def __neg__(self):
341-
return LIGOTimeGPS(0, -self.ns())
342-
343-
def __abs__(self):
344-
if self._seconds >= 0:
345-
return self
346-
return -self
36+
__author__ = "Duncan Macleod <[email protected]>"

0 commit comments

Comments
 (0)