|
16 | 16 | # You should have received a copy of the GNU General Public License |
17 | 17 | # along with ligotimegps. If not, see <http://www.gnu.org/licenses/>. |
18 | 18 |
|
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. |
22 | 23 |
|
23 | 24 | The code provided here is much slower than the C-implementation provided |
24 | 25 | by LAL, so if you really care about performance, don't use this module. |
25 | 26 | """ |
26 | 27 |
|
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 |
30 | 30 |
|
31 | 31 | try: |
32 | 32 | from ._version import version as __version__ |
33 | | -except ModuleNotFoundError: # development mode |
| 33 | +except ModuleNotFoundError: # pragma: no cover |
34 | 34 | __version__ = "" |
35 | 35 |
|
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