qpoint 1.13.0__cp313-cp313-macosx_10_13_universal2.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- qpoint/__init__.py +13 -0
- qpoint/_libqpoint.py +1286 -0
- qpoint/_version.py +34 -0
- qpoint/libqpoint.cpython-313-darwin.so +0 -0
- qpoint/qmap_class.py +1378 -0
- qpoint/qpoint_class.py +1974 -0
- qpoint/tools.py +35 -0
- qpoint-1.13.0.dist-info/METADATA +95 -0
- qpoint-1.13.0.dist-info/RECORD +12 -0
- qpoint-1.13.0.dist-info/WHEEL +6 -0
- qpoint-1.13.0.dist-info/licenses/LICENSE +21 -0
- qpoint-1.13.0.dist-info/top_level.txt +1 -0
qpoint/qpoint_class.py
ADDED
|
@@ -0,0 +1,1974 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from warnings import warn
|
|
3
|
+
from . import _libqpoint as lib
|
|
4
|
+
from ._libqpoint import libqp as qp
|
|
5
|
+
from ._libqpoint import check_input, check_inputs, check_output
|
|
6
|
+
|
|
7
|
+
__all__ = ["QPoint", "check_input", "check_inputs", "check_output"]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class QPoint(object):
|
|
11
|
+
def __init__(self, update_iers=False, **kwargs):
|
|
12
|
+
"""
|
|
13
|
+
Initialize a `QPoint` memory instance for keeping track of pointing
|
|
14
|
+
corrections over time.
|
|
15
|
+
|
|
16
|
+
If `update_iers` is `True`, attempt to call the method
|
|
17
|
+
:meth:`update_bulletin_a` to update the internal IERS-A database
|
|
18
|
+
(requires `astropy` version 1.2 or newer).
|
|
19
|
+
|
|
20
|
+
Any remaining keyword arguments are passed to :meth:`set` to update
|
|
21
|
+
memory.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
# initialize memory
|
|
25
|
+
self._memory = qp.qp_init_memory()
|
|
26
|
+
|
|
27
|
+
# try to update IERS-A bulletin
|
|
28
|
+
if update_iers:
|
|
29
|
+
self.update_bulletin_a()
|
|
30
|
+
|
|
31
|
+
# collect all parameter functions
|
|
32
|
+
self._funcs = lib.qp_funcs
|
|
33
|
+
self._all_funcs = dict()
|
|
34
|
+
for k in self._funcs:
|
|
35
|
+
self._all_funcs.update(**self._funcs[k])
|
|
36
|
+
|
|
37
|
+
# set any requested parameters
|
|
38
|
+
self.set(**kwargs)
|
|
39
|
+
|
|
40
|
+
def print_memory(self):
|
|
41
|
+
"""
|
|
42
|
+
Print current memory state in C.
|
|
43
|
+
"""
|
|
44
|
+
qp.qp_print_memory(self._memory)
|
|
45
|
+
|
|
46
|
+
def __del__(self):
|
|
47
|
+
"""
|
|
48
|
+
Free memory before deleting the object
|
|
49
|
+
"""
|
|
50
|
+
qp.qp_free_memory(self._memory)
|
|
51
|
+
|
|
52
|
+
def _set(self, key, val):
|
|
53
|
+
"""
|
|
54
|
+
Set a single parameter to the given value. See :meth:`set` for a list
|
|
55
|
+
of parameter names.
|
|
56
|
+
"""
|
|
57
|
+
if key not in self._all_funcs:
|
|
58
|
+
raise KeyError("Unknown parameter {}".format(key))
|
|
59
|
+
val = self._all_funcs[key]["check_set"](val)
|
|
60
|
+
self._all_funcs[key]["set"](self._memory, val)
|
|
61
|
+
|
|
62
|
+
def _get(self, key):
|
|
63
|
+
"""
|
|
64
|
+
Get the value for a single parameter. See :meth:`set` for a list of
|
|
65
|
+
parameter names.
|
|
66
|
+
"""
|
|
67
|
+
if key not in self._all_funcs:
|
|
68
|
+
raise KeyError("Unknown parameter {}".format(key))
|
|
69
|
+
val = self._all_funcs[key]["get"](self._memory)
|
|
70
|
+
return self._all_funcs[key]["check_get"](val)
|
|
71
|
+
|
|
72
|
+
def set(self, **kwargs):
|
|
73
|
+
"""
|
|
74
|
+
Set computation options.
|
|
75
|
+
|
|
76
|
+
Arguments
|
|
77
|
+
---------
|
|
78
|
+
rate_daber : {'never', 'once', 'always'}, or float
|
|
79
|
+
Rate at which the diurnal aberration correction is applied in seconds
|
|
80
|
+
(NB: this can only be applied always or never)
|
|
81
|
+
rate_lonlat : {'never', 'once', 'always'}, or float
|
|
82
|
+
Rate at which observer's lon and lat are updated
|
|
83
|
+
rate_wobble : {'never', 'once', 'always'}, or float
|
|
84
|
+
Rate at which the polar motion correction is updated
|
|
85
|
+
(NB: these are not estimated for dates beyond a year from now)
|
|
86
|
+
rate_dut1 : {'never', 'once', 'always'}, or float
|
|
87
|
+
Rate at which the ut1-utc correction is updated
|
|
88
|
+
(NB: this is not estimated for dates beyond a year from now)
|
|
89
|
+
rate_erot : {'never', 'once', 'always'}, or float
|
|
90
|
+
Rate at which the earth's rotation angle is updated
|
|
91
|
+
rate_npb : {'never', 'once', 'always'}, or float
|
|
92
|
+
Rate at which the nutation/precession/frame-bias terms are updated
|
|
93
|
+
rate_aaber : {'never', 'once', 'always'}, or float
|
|
94
|
+
Rate at which the annual aberration correction
|
|
95
|
+
(due to the earth's orbital velocity) is updated
|
|
96
|
+
rate_ref : {'never', 'once', 'always'}, or float
|
|
97
|
+
Rate at which the refaction correction is updated (NB: this
|
|
98
|
+
correction can also be updated manually -- see :meth:`refraction`)
|
|
99
|
+
accuracy : 'low' or 'high'
|
|
100
|
+
If 'low', use a truncated form (2000b) for the NPB correction,
|
|
101
|
+
which is much faster but less accurate. If 'high' (default), use
|
|
102
|
+
the full 2006/2000a form.
|
|
103
|
+
mean_aber : bool
|
|
104
|
+
If True, apply the aberration correction as an average for the
|
|
105
|
+
entire field of view. This is gives a 1-2 arcsec deviation
|
|
106
|
+
at the edges of the SPIDER field of view.
|
|
107
|
+
fast_aber : bool
|
|
108
|
+
If True, apply the aberration correction using the exact
|
|
109
|
+
angle and quaternion rotation. Otherwise, use a small-angle
|
|
110
|
+
approximation for the aberration quaternion (default).
|
|
111
|
+
fast_math : bool
|
|
112
|
+
If True, use polynomial approximations for trig functions
|
|
113
|
+
polconv : 'cosmo' or 'iau'
|
|
114
|
+
Specify the 'cosmo' or 'iau' polarization convention
|
|
115
|
+
pix_order : 'nest' or 'ring'
|
|
116
|
+
HEALPix pixel ordering
|
|
117
|
+
interp_pix : bool
|
|
118
|
+
If True, interpolate between pixels in scanning the source map.
|
|
119
|
+
fast_pix : bool
|
|
120
|
+
If True, use `vec2pix` to get pixel number directly from the
|
|
121
|
+
quaternion instead of `ang2pix` from ra/dec.
|
|
122
|
+
error_missing : bool
|
|
123
|
+
If True, raise an error if reading/writing missing pixels.
|
|
124
|
+
nan_missing : bool
|
|
125
|
+
If True, fill samples from missing pixels with NaN.
|
|
126
|
+
Only used if `error_missing` is False.
|
|
127
|
+
interp_missing : bool
|
|
128
|
+
If True and `interp_pix` is True, drop missing neighbors
|
|
129
|
+
and reweight remaining neighbors. Overrides `nan_missing`.
|
|
130
|
+
num_threads : bool
|
|
131
|
+
Number of openMP threads to use for mapmaking.
|
|
132
|
+
temperature : float
|
|
133
|
+
Ambient temperature, Celcius. For computing refraction corrections.
|
|
134
|
+
pressure : float
|
|
135
|
+
Ambient pressure, mbar. For computing refraction corrections.
|
|
136
|
+
humidity : float
|
|
137
|
+
Relative humidity, fraction. For computing refraction corrections.
|
|
138
|
+
frequency : float
|
|
139
|
+
Observer frequency, GHz. For computing refraction corrections.
|
|
140
|
+
dut1 : float
|
|
141
|
+
UT1 correction
|
|
142
|
+
ref_delta : float
|
|
143
|
+
Refraction correction
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
for k in kwargs:
|
|
147
|
+
try:
|
|
148
|
+
self._set(k, kwargs[k])
|
|
149
|
+
except KeyError:
|
|
150
|
+
continue
|
|
151
|
+
|
|
152
|
+
def get(self, *args):
|
|
153
|
+
"""
|
|
154
|
+
Returns a dictionary of the requested state parameters. If no
|
|
155
|
+
parameters are supplied, then all are returned. If a single parameter
|
|
156
|
+
is supplied, then just that value is returned. See :meth:`set` for a
|
|
157
|
+
list of parameter names.
|
|
158
|
+
|
|
159
|
+
Can also select 'options', 'rates', 'weather', or 'params' to return
|
|
160
|
+
all of that subset of parameters.
|
|
161
|
+
"""
|
|
162
|
+
state = dict()
|
|
163
|
+
if not len(args):
|
|
164
|
+
for k in self._funcs:
|
|
165
|
+
state[k] = dict()
|
|
166
|
+
for kk in self._funcs[k]:
|
|
167
|
+
state[k][kk] = self._get(kk)
|
|
168
|
+
return state
|
|
169
|
+
|
|
170
|
+
for arg in args:
|
|
171
|
+
if arg in self._funcs:
|
|
172
|
+
state[arg] = dict()
|
|
173
|
+
for k in self._funcs[arg]:
|
|
174
|
+
state[arg][k] = self._get(k)
|
|
175
|
+
else:
|
|
176
|
+
state[arg] = self._get(arg)
|
|
177
|
+
if len(args) == 1:
|
|
178
|
+
return state[args[0]]
|
|
179
|
+
return state
|
|
180
|
+
|
|
181
|
+
def reset_rates(self):
|
|
182
|
+
"""
|
|
183
|
+
Reset update counters for each state. Useful to force an updated
|
|
184
|
+
correction term at the beginning of each chunk.
|
|
185
|
+
"""
|
|
186
|
+
qp.qp_reset_rates(self._memory)
|
|
187
|
+
|
|
188
|
+
def reset_inv_rates(self):
|
|
189
|
+
"""
|
|
190
|
+
Reset update counters for each inverse state. Useful to force an updated
|
|
191
|
+
correction term at the beginning of each chunk.
|
|
192
|
+
"""
|
|
193
|
+
qp.qp_reset_inv_rates(self._memory)
|
|
194
|
+
|
|
195
|
+
def refraction(self, *args, **kwargs):
|
|
196
|
+
"""refraction(q, **kwargs)
|
|
197
|
+
refraction(delta)
|
|
198
|
+
|
|
199
|
+
Update refraction parameters
|
|
200
|
+
|
|
201
|
+
Arguments
|
|
202
|
+
---------
|
|
203
|
+
q : quaternion or array of quaternions
|
|
204
|
+
Observer orientation in horizon coordinates
|
|
205
|
+
temperature : float
|
|
206
|
+
Ambient temperature, Celcius
|
|
207
|
+
pressure : float
|
|
208
|
+
Ambient pressure, mbar
|
|
209
|
+
humidity : float
|
|
210
|
+
Ambient relative humidity, fraction
|
|
211
|
+
frequency : float
|
|
212
|
+
Observing frequency, GHz
|
|
213
|
+
delta : float
|
|
214
|
+
The refraction correction itself, in degrees
|
|
215
|
+
|
|
216
|
+
Returns
|
|
217
|
+
-------
|
|
218
|
+
delta : array_like
|
|
219
|
+
Refraction correction computed at each input orientation
|
|
220
|
+
|
|
221
|
+
Notes
|
|
222
|
+
-----
|
|
223
|
+
If `q` is given, then the refraction correction in degrees
|
|
224
|
+
is calculated, stored and returned after updating any other given
|
|
225
|
+
parameters. Otherwise, the correction is returned w/out recalculating.
|
|
226
|
+
|
|
227
|
+
Alternatively, if a single numerical argument, or the `delta` keyword
|
|
228
|
+
argument is given, then the correction is stored with this value
|
|
229
|
+
instead of being recalculated.
|
|
230
|
+
|
|
231
|
+
Numpy-vectorized for the `q` argument. Note that this is not
|
|
232
|
+
an efficient vectorization, and only the last calculated value is
|
|
233
|
+
stored for use in the coordinate conversion functions.
|
|
234
|
+
"""
|
|
235
|
+
|
|
236
|
+
if len(args) == 1 and len(kwargs) == 0:
|
|
237
|
+
v = args[0]
|
|
238
|
+
if np.isscalar(v):
|
|
239
|
+
self._set("ref_delta", v)
|
|
240
|
+
return v
|
|
241
|
+
|
|
242
|
+
if "delta" in kwargs:
|
|
243
|
+
v = kwargs.get("delta")
|
|
244
|
+
self._set("ref_delta", v)
|
|
245
|
+
return v
|
|
246
|
+
|
|
247
|
+
arg_names = ["q"] + list(self._funcs["weather"])
|
|
248
|
+
for idx, a in enumerate(args):
|
|
249
|
+
kwargs[arg_names[idx]] = a
|
|
250
|
+
|
|
251
|
+
for w in self._funcs["weather"]:
|
|
252
|
+
if w in kwargs:
|
|
253
|
+
self._set(w, kwargs.get(w))
|
|
254
|
+
|
|
255
|
+
q = kwargs.get("q", None)
|
|
256
|
+
if q is not None:
|
|
257
|
+
|
|
258
|
+
def func(x0, x1, x2, x3):
|
|
259
|
+
q = np.ascontiguousarray([x0, x1, x2, x3])
|
|
260
|
+
return qp.qp_update_ref(self._memory, q)
|
|
261
|
+
|
|
262
|
+
fvec = np.vectorize(func, [np.double])
|
|
263
|
+
if q.size // 4 > 1:
|
|
264
|
+
q = q.transpose()
|
|
265
|
+
delta = fvec(q[0], q[1], q[2], q[3])
|
|
266
|
+
if delta.shape == ():
|
|
267
|
+
return delta[()]
|
|
268
|
+
return delta
|
|
269
|
+
return self._get("ref_delta")
|
|
270
|
+
|
|
271
|
+
def gmst(self, ctime, **kwargs):
|
|
272
|
+
"""
|
|
273
|
+
Return Greenwich mean sidereal time for given ctimes.
|
|
274
|
+
Vectorized.
|
|
275
|
+
|
|
276
|
+
Arguments
|
|
277
|
+
---------
|
|
278
|
+
ctime : array_like
|
|
279
|
+
Unix time in seconds UTC
|
|
280
|
+
|
|
281
|
+
Returns
|
|
282
|
+
-------
|
|
283
|
+
gmst : array_like
|
|
284
|
+
Greenwich mean sidereal time of the observer
|
|
285
|
+
|
|
286
|
+
Notes
|
|
287
|
+
-----
|
|
288
|
+
Any keywords accepted by the :meth:`set` method can also be passed here,
|
|
289
|
+
and will be processed prior to calculation.
|
|
290
|
+
"""
|
|
291
|
+
|
|
292
|
+
self.set(**kwargs)
|
|
293
|
+
|
|
294
|
+
ctime = check_input("ctime", np.atleast_1d(ctime))
|
|
295
|
+
n = ctime.size
|
|
296
|
+
|
|
297
|
+
gmst = check_output("gmst", shape=ctime.shape)
|
|
298
|
+
qp.qp_gmstn(self._memory, ctime, gmst, n)
|
|
299
|
+
|
|
300
|
+
if n == 1:
|
|
301
|
+
return gmst[0]
|
|
302
|
+
return gmst
|
|
303
|
+
|
|
304
|
+
def lmst(self, ctime, lon, **kwargs):
|
|
305
|
+
"""
|
|
306
|
+
Return local mean sidereal time for given ctimes and longitudes.
|
|
307
|
+
Vectorized, input arguments must be broadcastable to the same shape.
|
|
308
|
+
|
|
309
|
+
Arguments
|
|
310
|
+
---------
|
|
311
|
+
ctime : array_like
|
|
312
|
+
Unix time in seconds UTC
|
|
313
|
+
lon : array_like
|
|
314
|
+
Observer longitude (degrees)
|
|
315
|
+
|
|
316
|
+
Returns
|
|
317
|
+
-------
|
|
318
|
+
lmst : array_like
|
|
319
|
+
Local mean sidereal time of the observer
|
|
320
|
+
|
|
321
|
+
Notes
|
|
322
|
+
-----
|
|
323
|
+
Any keywords accepted by the :meth:`set` method can also be passed here,
|
|
324
|
+
and will be processed prior to calculation.
|
|
325
|
+
"""
|
|
326
|
+
|
|
327
|
+
self.set(**kwargs)
|
|
328
|
+
|
|
329
|
+
ctime, lon = check_inputs(ctime, lon)
|
|
330
|
+
n = ctime.size
|
|
331
|
+
|
|
332
|
+
lmst = check_output("lmst", shape=ctime.shape)
|
|
333
|
+
qp.qp_lmstn(self._memory, ctime, lon, lmst, n)
|
|
334
|
+
|
|
335
|
+
if n == 1:
|
|
336
|
+
return lmst[0]
|
|
337
|
+
return lmst
|
|
338
|
+
|
|
339
|
+
def dipole(self, ctime, ra, dec, **kwargs):
|
|
340
|
+
"""
|
|
341
|
+
Return dipole amplitude in the given equatorial direction.
|
|
342
|
+
Vectorized, input arguments must be broadcastable to the same shape.
|
|
343
|
+
|
|
344
|
+
Arguments
|
|
345
|
+
---------
|
|
346
|
+
ctime : array_like
|
|
347
|
+
Unix time in seconds UTC
|
|
348
|
+
ra : array_like
|
|
349
|
+
Right ascension on the sky, in degrees.
|
|
350
|
+
dec : array_like
|
|
351
|
+
Declination on the sky, in degrees
|
|
352
|
+
|
|
353
|
+
Returns
|
|
354
|
+
-------
|
|
355
|
+
dipole : array_like
|
|
356
|
+
Dipole amplitude in K
|
|
357
|
+
|
|
358
|
+
Notes
|
|
359
|
+
-----
|
|
360
|
+
Any keywords accepted by the :meth:`set` method can also be passed here,
|
|
361
|
+
and will be processed prior to calculation.
|
|
362
|
+
"""
|
|
363
|
+
|
|
364
|
+
self.set(**kwargs)
|
|
365
|
+
|
|
366
|
+
ctime, ra, dec = check_inputs(ctime, ra, dec)
|
|
367
|
+
n = ctime.size
|
|
368
|
+
|
|
369
|
+
dipole = check_output("dipole", shape=ctime.shape, **kwargs)
|
|
370
|
+
qp.qp_dipolen(self._memory, ctime, ra, dec, dipole, n)
|
|
371
|
+
|
|
372
|
+
if n == 1:
|
|
373
|
+
return dipole[0]
|
|
374
|
+
return dipole
|
|
375
|
+
|
|
376
|
+
def bore2dipole(self, q_off, ctime, q_bore, **kwargs):
|
|
377
|
+
"""
|
|
378
|
+
Calculate dipole timestream for given offset and boresight pointing.
|
|
379
|
+
|
|
380
|
+
Arguments
|
|
381
|
+
---------
|
|
382
|
+
q_off : quaternion
|
|
383
|
+
Detector offset quaternion for a single detector, calculated using
|
|
384
|
+
:meth:`det_offset`
|
|
385
|
+
ctime : array_like
|
|
386
|
+
Array of unix times in seconds UTC
|
|
387
|
+
q_bore : quaternion or array of quaternions
|
|
388
|
+
Array of quaternions encoding the boresight orientation on the sky
|
|
389
|
+
(as output by :meth:`azel2radec` or similar). Broadcastable to the
|
|
390
|
+
same length as `ctime`.
|
|
391
|
+
|
|
392
|
+
Returns
|
|
393
|
+
-------
|
|
394
|
+
dipole : array_like
|
|
395
|
+
Dipole amplitude in K
|
|
396
|
+
|
|
397
|
+
Notes
|
|
398
|
+
-----
|
|
399
|
+
Any keywords accepted by the :meth:`set` method can also be passed here,
|
|
400
|
+
and will be processed prior to calculation.
|
|
401
|
+
"""
|
|
402
|
+
|
|
403
|
+
self.set(**kwargs)
|
|
404
|
+
|
|
405
|
+
q_off = check_input("q_off", q_off, shape=(4,), quat=True)
|
|
406
|
+
ctime = check_input("ctime", ctime)
|
|
407
|
+
n = ctime.size
|
|
408
|
+
q_bore = check_input("q_bore", q_bore, shape=(n, 4), quat=True)
|
|
409
|
+
|
|
410
|
+
dipole = check_output("dipole", shape=ctime.shape, **kwargs)
|
|
411
|
+
qp.qp_bore2dipole(self._memory, q_off, ctime, q_bore, dipole, n)
|
|
412
|
+
|
|
413
|
+
if n == 1:
|
|
414
|
+
return dipole[0]
|
|
415
|
+
return dipole
|
|
416
|
+
|
|
417
|
+
def det_offset(self, delta_az, delta_el, delta_psi):
|
|
418
|
+
"""
|
|
419
|
+
Return quaternion corresponding to the requested detector centroid
|
|
420
|
+
offset from boresight. Vectorized, input arguments must be
|
|
421
|
+
broadcastable to the same shape.
|
|
422
|
+
|
|
423
|
+
Arguments
|
|
424
|
+
---------
|
|
425
|
+
delta_az : array_like
|
|
426
|
+
Azimuthal centroid offset of the detector in degrees
|
|
427
|
+
delta_el : array_like
|
|
428
|
+
Elevation centroid offset of the detector in degrees
|
|
429
|
+
delta_psi : array_like
|
|
430
|
+
Polarization offset of the detector from vertical in degrees
|
|
431
|
+
|
|
432
|
+
Returns
|
|
433
|
+
-------
|
|
434
|
+
q : array_like
|
|
435
|
+
Detector centroid offset quaternion for each detector
|
|
436
|
+
"""
|
|
437
|
+
|
|
438
|
+
delta_az, delta_el, delta_psi = check_inputs(delta_az, delta_el, delta_psi)
|
|
439
|
+
ndet = delta_az.size
|
|
440
|
+
|
|
441
|
+
quat = check_output("quat", shape=(ndet, 4))
|
|
442
|
+
qp.qp_det_offsetn(delta_az, delta_el, delta_psi, quat, ndet)
|
|
443
|
+
|
|
444
|
+
if ndet == 1:
|
|
445
|
+
return quat[0]
|
|
446
|
+
return quat
|
|
447
|
+
|
|
448
|
+
def bore_offset(
|
|
449
|
+
self, q_bore, ang1=None, ang2=None, ang3=None, post=False, inplace=False
|
|
450
|
+
):
|
|
451
|
+
"""
|
|
452
|
+
Apply a fixed or variable offset to the boresight quaternion.
|
|
453
|
+
Input arguments must be broadcastable to the same shape.
|
|
454
|
+
|
|
455
|
+
Arguments
|
|
456
|
+
---------
|
|
457
|
+
q_bore : array_like
|
|
458
|
+
boresight pointing quaternion
|
|
459
|
+
ang1 : array_like, optional
|
|
460
|
+
Azimuthal or ra offset in degrees
|
|
461
|
+
ang2 : array_like, optional
|
|
462
|
+
Elevation or dec offset in degrees
|
|
463
|
+
ang3 : array_like, optional
|
|
464
|
+
Position angle offset in degrees
|
|
465
|
+
post : bool, optional
|
|
466
|
+
If False, apply offset as an az/el/pa pre-rotation
|
|
467
|
+
If True, apply offset as an ra/dec/pa post-rotation
|
|
468
|
+
inplace : bool, optional
|
|
469
|
+
If True, apply the rotation in-place in memory.
|
|
470
|
+
|
|
471
|
+
Returns
|
|
472
|
+
-------
|
|
473
|
+
q_bore : array_like
|
|
474
|
+
Offset boresight quaternion
|
|
475
|
+
"""
|
|
476
|
+
q_bore = check_input(
|
|
477
|
+
"q_bore", np.atleast_2d(q_bore), quat=True, inplace=inplace, output=True
|
|
478
|
+
)
|
|
479
|
+
n = q_bore.shape[0]
|
|
480
|
+
if all([a is None for a in [ang1, ang2, ang3]]):
|
|
481
|
+
raise ValueError("One of ang1, ang2, ang3 is required")
|
|
482
|
+
ang1, ang2, ang3 = check_inputs(ang1, ang2, ang3, shape=(n,))
|
|
483
|
+
qp.qp_bore_offset(self._memory, q_bore, ang1, ang2, ang3, n, int(post))
|
|
484
|
+
return q_bore
|
|
485
|
+
|
|
486
|
+
def hwp_quat(self, theta):
|
|
487
|
+
"""
|
|
488
|
+
Return quaternion corresponding to rotation by 2 * theta, where theta is
|
|
489
|
+
the physical waveplate angle. Vectorized.
|
|
490
|
+
|
|
491
|
+
Arguments
|
|
492
|
+
---------
|
|
493
|
+
theta : array_like
|
|
494
|
+
HWP physical angle in degrees
|
|
495
|
+
|
|
496
|
+
Returns
|
|
497
|
+
-------
|
|
498
|
+
q : array_like
|
|
499
|
+
Quaternion for each hwp angle
|
|
500
|
+
"""
|
|
501
|
+
theta = check_input("theta", np.atleast_1d(theta))
|
|
502
|
+
n = theta.size
|
|
503
|
+
|
|
504
|
+
quat = check_output("quat", shape=(n, 4))
|
|
505
|
+
qp.qp_hwp_quatn(theta, quat, n)
|
|
506
|
+
|
|
507
|
+
if n == 1:
|
|
508
|
+
return quat[0]
|
|
509
|
+
return quat
|
|
510
|
+
|
|
511
|
+
def azel2bore(self, az, el, pitch, roll, lon, lat, ctime, q=None, **kwargs):
|
|
512
|
+
"""
|
|
513
|
+
Estimate the quaternion for the boresight orientation on the sky given
|
|
514
|
+
the attitude (az/el/pitch/roll), location on the earth (lon/lat) and
|
|
515
|
+
ctime. Input vectors must be numpy-array-like and broadcastable to the
|
|
516
|
+
same shape.
|
|
517
|
+
|
|
518
|
+
Arguments
|
|
519
|
+
---------
|
|
520
|
+
az : array_like
|
|
521
|
+
Boresight azimuth in degrees
|
|
522
|
+
el : array_like
|
|
523
|
+
Boresight elevation in degrees
|
|
524
|
+
pitch : array_like
|
|
525
|
+
Boresight pitch in degrees. If `None`, this term is ignored.
|
|
526
|
+
roll : array_like
|
|
527
|
+
Boresight roll in degrees. If `None`, this term is ignored.
|
|
528
|
+
lon : array_like
|
|
529
|
+
Observer longitude in degrees
|
|
530
|
+
lat : array_like
|
|
531
|
+
Observer latitude in degrees
|
|
532
|
+
ctime : array_like
|
|
533
|
+
Unix time in seconds UTC
|
|
534
|
+
q : array_like, optional
|
|
535
|
+
Output quaternion array initialized by user. Supply this
|
|
536
|
+
for in-place computation.
|
|
537
|
+
|
|
538
|
+
Returns
|
|
539
|
+
-------
|
|
540
|
+
q : array_like
|
|
541
|
+
Nx4 numpy array of quaternions for each supplied timestamp.
|
|
542
|
+
|
|
543
|
+
Notes
|
|
544
|
+
-----
|
|
545
|
+
Any keywords accepted by the :meth:`set` method can also be passed here,
|
|
546
|
+
and will be processed prior to calculation.
|
|
547
|
+
"""
|
|
548
|
+
|
|
549
|
+
self.set(**kwargs)
|
|
550
|
+
|
|
551
|
+
az, el, pitch, roll, lon, lat, ctime = check_inputs(
|
|
552
|
+
az, el, pitch, roll, lon, lat, ctime
|
|
553
|
+
)
|
|
554
|
+
n = az.size
|
|
555
|
+
|
|
556
|
+
# identity quaternion
|
|
557
|
+
q = check_output("q", q, shape=(n, 4), fill=[1, 0, 0, 0])
|
|
558
|
+
|
|
559
|
+
qp.qp_azel2bore(self._memory, az, el, pitch, roll, lon, lat, ctime, q, n)
|
|
560
|
+
|
|
561
|
+
return q
|
|
562
|
+
|
|
563
|
+
def azelpsi2bore(self, az, el, psi, pitch, roll, lon, lat, ctime, q=None, **kwargs):
|
|
564
|
+
"""
|
|
565
|
+
Estimate the quaternion for the boresight orientation on the sky given
|
|
566
|
+
the attitude (az/el/psi/pitch/roll), location on the earth (lon/lat) and
|
|
567
|
+
ctime. Input vectors must be numpy-array-like and broadcastable to the
|
|
568
|
+
same shape.
|
|
569
|
+
|
|
570
|
+
This is an augmented version of azel2bore to accept rotations of the focal
|
|
571
|
+
plane about the optical axis.
|
|
572
|
+
|
|
573
|
+
Arguments
|
|
574
|
+
---------
|
|
575
|
+
az : array_like
|
|
576
|
+
Boresight azimuth in degrees
|
|
577
|
+
el : array_like
|
|
578
|
+
Boresight elevation in degrees
|
|
579
|
+
psi : array_like
|
|
580
|
+
Boresight rotation angle in degrees
|
|
581
|
+
pitch : array_like
|
|
582
|
+
Boresight pitch in degrees. If `None`, this term is ignored.
|
|
583
|
+
roll : array_like
|
|
584
|
+
Boresight roll in degrees. If `None`, this term is ignored.
|
|
585
|
+
lon : array_like
|
|
586
|
+
Observer longitude in degrees
|
|
587
|
+
lat : array_like
|
|
588
|
+
Observer latitude in degrees
|
|
589
|
+
ctime : array_like
|
|
590
|
+
Unix time in seconds UTC
|
|
591
|
+
q : array_like, optional
|
|
592
|
+
Output quaternion array initialized by user. Supply this
|
|
593
|
+
for in-place computation.
|
|
594
|
+
|
|
595
|
+
Returns
|
|
596
|
+
-------
|
|
597
|
+
q : array_like
|
|
598
|
+
Nx4 numpy array of quaternions for each supplied timestamp.
|
|
599
|
+
|
|
600
|
+
Notes
|
|
601
|
+
-----
|
|
602
|
+
Any keywords accepted by the :meth:`set` method can also be passed here,
|
|
603
|
+
and will be processed prior to calculation.
|
|
604
|
+
"""
|
|
605
|
+
|
|
606
|
+
self.set(**kwargs)
|
|
607
|
+
|
|
608
|
+
az, el, psi, pitch, roll, lon, lat, ctime = check_inputs(
|
|
609
|
+
az, el, psi, pitch, roll, lon, lat, ctime
|
|
610
|
+
)
|
|
611
|
+
n = az.size
|
|
612
|
+
|
|
613
|
+
# identity quaternion
|
|
614
|
+
q = check_output("q", q, shape=(n, 4), fill=[1, 0, 0, 0])
|
|
615
|
+
|
|
616
|
+
qp.qp_azelpsi2bore(
|
|
617
|
+
self._memory, az, el, psi, pitch, roll, lon, lat, ctime, q, n
|
|
618
|
+
)
|
|
619
|
+
|
|
620
|
+
return q
|
|
621
|
+
|
|
622
|
+
def bore2radec(
|
|
623
|
+
self,
|
|
624
|
+
q_off,
|
|
625
|
+
ctime,
|
|
626
|
+
q_bore,
|
|
627
|
+
q_hwp=None,
|
|
628
|
+
sindec=False,
|
|
629
|
+
return_pa=False,
|
|
630
|
+
ra=None,
|
|
631
|
+
dec=None,
|
|
632
|
+
pa=None,
|
|
633
|
+
sin2psi=None,
|
|
634
|
+
cos2psi=None,
|
|
635
|
+
**kwargs,
|
|
636
|
+
):
|
|
637
|
+
"""
|
|
638
|
+
Calculate the orientation on the sky for a detector offset from the
|
|
639
|
+
boresight. Detector offsets are defined assuming the boresight is
|
|
640
|
+
pointed toward the horizon, and that the boresight polarization axis is
|
|
641
|
+
along the vertical.
|
|
642
|
+
|
|
643
|
+
Arguments
|
|
644
|
+
---------
|
|
645
|
+
q_off : quaternion
|
|
646
|
+
Detector offset quaternion for a single detector, calculated using
|
|
647
|
+
:meth:`det_offset`.
|
|
648
|
+
ctime : array_like
|
|
649
|
+
Unix time in seconds UTC, broadcastable to shape (N,),
|
|
650
|
+
the long dimension of `q_bore`.
|
|
651
|
+
q_bore : quaternion or array of quaternions
|
|
652
|
+
Nx4 array of quaternions encoding the boresight orientation on the
|
|
653
|
+
sky (as output by :meth:`azel2radec` or equivalent)
|
|
654
|
+
q_hwp : quaternion or array of quaternions, optional
|
|
655
|
+
HWP angle quaternions calculated using :meth:`hwp_quat`. Must be
|
|
656
|
+
broadcastable to the same shape as `q_bore`.
|
|
657
|
+
sindec : bool, optional
|
|
658
|
+
If `True`, return sin(dec) instead of dec in degrees
|
|
659
|
+
(default False).
|
|
660
|
+
return_pa : bool, optional
|
|
661
|
+
If `True`, return pa instead of sin2psi / cos2psi
|
|
662
|
+
|
|
663
|
+
Returns
|
|
664
|
+
-------
|
|
665
|
+
ra : array_like
|
|
666
|
+
Detector right ascension in degrees
|
|
667
|
+
dec/sindec : array_like
|
|
668
|
+
Detector declination in degrees or sin(dec) if `sindec` is `True`.
|
|
669
|
+
pa/sin2psi : array_like
|
|
670
|
+
Detector polarization orientation if `return_pa` is `True`, or
|
|
671
|
+
sin(2*pa) if `return_pa` is `False`.
|
|
672
|
+
cos2psi : array_like
|
|
673
|
+
detector polarization orientation cos(2*pa), if `return_pa` is `False`.
|
|
674
|
+
|
|
675
|
+
Notes
|
|
676
|
+
-----
|
|
677
|
+
Any keywords accepted by the :meth:`set` method can also be passed here,
|
|
678
|
+
and will be processed prior to calculation.
|
|
679
|
+
|
|
680
|
+
Pre-allocated output arguments can also be supplied as input keywords
|
|
681
|
+
for in-place operation.
|
|
682
|
+
"""
|
|
683
|
+
|
|
684
|
+
self.set(**kwargs)
|
|
685
|
+
|
|
686
|
+
q_off = check_input("q_off", q_off, quat=True)
|
|
687
|
+
q_bore = check_input("q_bore", np.atleast_2d(q_bore), quat=True)
|
|
688
|
+
shape = (q_bore.size // 4,)
|
|
689
|
+
if ctime is None:
|
|
690
|
+
if not self.get("mean_aber"):
|
|
691
|
+
raise ValueError("ctime required if mean_aber is False")
|
|
692
|
+
ctime = np.zeros(shape, dtype=q_bore.dtype)
|
|
693
|
+
ctime = check_input("ctime", ctime, shape=shape)
|
|
694
|
+
pars = dict(shape=ctime.shape, dtype=np.double)
|
|
695
|
+
ra = check_output("ra", ra, **pars)
|
|
696
|
+
dec = check_output("dec", dec, **pars)
|
|
697
|
+
if return_pa:
|
|
698
|
+
if sindec:
|
|
699
|
+
raise ValueError("Cannot use sindec with return_pa=True")
|
|
700
|
+
pa = check_output("pa", pa, **pars)
|
|
701
|
+
else:
|
|
702
|
+
sin2psi = check_output("sin2psi", sin2psi, **pars)
|
|
703
|
+
cos2psi = check_output("cos2psi", cos2psi, **pars)
|
|
704
|
+
n = ctime.size
|
|
705
|
+
|
|
706
|
+
if q_hwp is None:
|
|
707
|
+
if return_pa:
|
|
708
|
+
qp.qp_bore2radecpa(self._memory, q_off, ctime, q_bore, ra, dec, pa, n)
|
|
709
|
+
elif sindec:
|
|
710
|
+
qp.qp_bore2rasindec(
|
|
711
|
+
self._memory, q_off, ctime, q_bore, ra, dec, sin2psi, cos2psi, n
|
|
712
|
+
)
|
|
713
|
+
else:
|
|
714
|
+
qp.qp_bore2radec(
|
|
715
|
+
self._memory, q_off, ctime, q_bore, ra, dec, sin2psi, cos2psi, n
|
|
716
|
+
)
|
|
717
|
+
else:
|
|
718
|
+
q_hwp = check_input("q_hwp", q_hwp, shape=q_bore.shape)
|
|
719
|
+
if return_pa:
|
|
720
|
+
qp.qp_bore2radecpa_hwp(
|
|
721
|
+
self._memory, q_off, ctime, q_bore, q_hwp, ra, dec, pa, n
|
|
722
|
+
)
|
|
723
|
+
elif sindec:
|
|
724
|
+
qp.qp_bore2rasindec_hwp(
|
|
725
|
+
self._memory,
|
|
726
|
+
q_off,
|
|
727
|
+
ctime,
|
|
728
|
+
q_bore,
|
|
729
|
+
q_hwp,
|
|
730
|
+
ra,
|
|
731
|
+
dec,
|
|
732
|
+
sin2psi,
|
|
733
|
+
cos2psi,
|
|
734
|
+
n,
|
|
735
|
+
)
|
|
736
|
+
else:
|
|
737
|
+
qp.qp_bore2radec_hwp(
|
|
738
|
+
self._memory,
|
|
739
|
+
q_off,
|
|
740
|
+
ctime,
|
|
741
|
+
q_bore,
|
|
742
|
+
q_hwp,
|
|
743
|
+
ra,
|
|
744
|
+
dec,
|
|
745
|
+
sin2psi,
|
|
746
|
+
cos2psi,
|
|
747
|
+
n,
|
|
748
|
+
)
|
|
749
|
+
|
|
750
|
+
if return_pa:
|
|
751
|
+
if n == 1:
|
|
752
|
+
return ra[0], dec[0], pa[0]
|
|
753
|
+
return ra, dec, pa
|
|
754
|
+
|
|
755
|
+
if n == 1:
|
|
756
|
+
return ra[0], dec[0], sin2psi[0], cos2psi[0]
|
|
757
|
+
return ra, dec, sin2psi, cos2psi
|
|
758
|
+
|
|
759
|
+
def azel2radec(
|
|
760
|
+
self,
|
|
761
|
+
delta_az,
|
|
762
|
+
delta_el,
|
|
763
|
+
delta_psi,
|
|
764
|
+
az,
|
|
765
|
+
el,
|
|
766
|
+
pitch,
|
|
767
|
+
roll,
|
|
768
|
+
lon,
|
|
769
|
+
lat,
|
|
770
|
+
ctime,
|
|
771
|
+
hwp=None,
|
|
772
|
+
sindec=False,
|
|
773
|
+
return_pa=False,
|
|
774
|
+
ra=None,
|
|
775
|
+
dec=None,
|
|
776
|
+
pa=None,
|
|
777
|
+
sin2psi=None,
|
|
778
|
+
cos2psi=None,
|
|
779
|
+
**kwargs,
|
|
780
|
+
):
|
|
781
|
+
"""
|
|
782
|
+
Estimate the orientation on the sky for a detector offset from
|
|
783
|
+
boresight, given the boresight attitude (az/el/pitch/roll), location on
|
|
784
|
+
the earth (lon/lat) and UTC time. Input vectors must be
|
|
785
|
+
numpy-array-like and broadcastable to the same shape. Detector offsets
|
|
786
|
+
are defined assuming the boresight is pointed toward the horizon, and
|
|
787
|
+
that the boresight polarization axis is along the horizontal.
|
|
788
|
+
|
|
789
|
+
Arguments
|
|
790
|
+
---------
|
|
791
|
+
delta_az : float
|
|
792
|
+
Azimuthal offset of the detector in degrees
|
|
793
|
+
delta_el : float
|
|
794
|
+
Elevation offset of the detector in degrees
|
|
795
|
+
delta_psi : float
|
|
796
|
+
Polarization offset of the detector in degrees
|
|
797
|
+
az : array_like
|
|
798
|
+
Boresight azimuth in degrees
|
|
799
|
+
el : array_like
|
|
800
|
+
Boresight elevation in degrees
|
|
801
|
+
pitch : array_like
|
|
802
|
+
Boresight pitch in degrees. If None, this term is ignored.
|
|
803
|
+
roll : array_like
|
|
804
|
+
Boresight roll in degrees. If None, this term is ignored.
|
|
805
|
+
lon : array_like
|
|
806
|
+
Observer longitude in degrees.
|
|
807
|
+
lat : array_like
|
|
808
|
+
Observer latitude in degrees.
|
|
809
|
+
ctime : array_like
|
|
810
|
+
Unix time in seconds UTC
|
|
811
|
+
hwp : array_like, optional
|
|
812
|
+
HWP angles in degrees
|
|
813
|
+
sindec : bool, optional
|
|
814
|
+
If `True`, return sin(dec) instead of dec in degrees (default False)
|
|
815
|
+
return_pa : bool, optional
|
|
816
|
+
If `True`, return pa instead of sin2psi/cos2psi
|
|
817
|
+
|
|
818
|
+
Returns
|
|
819
|
+
-------
|
|
820
|
+
ra : array_like
|
|
821
|
+
Detector right ascension in degrees
|
|
822
|
+
dec/sindec : array_like
|
|
823
|
+
Detector declination in degrees
|
|
824
|
+
pa : array_like
|
|
825
|
+
Detector position angle, if `return_pa` is True
|
|
826
|
+
sin2psi : array_like
|
|
827
|
+
Detector polarization orientation, if `return_pa` is False
|
|
828
|
+
cos2psi : array_like
|
|
829
|
+
Detector polarization orientation, if `return_pa` is False
|
|
830
|
+
|
|
831
|
+
Notes
|
|
832
|
+
-----
|
|
833
|
+
Any keywords accepted by the :meth:`set` method can also be passed here,
|
|
834
|
+
and will be processed prior to calculation.
|
|
835
|
+
"""
|
|
836
|
+
|
|
837
|
+
self.set(**kwargs)
|
|
838
|
+
|
|
839
|
+
az, el, pitch, roll, lon, lat, ctime = check_inputs(
|
|
840
|
+
az, el, pitch, roll, lon, lat, ctime
|
|
841
|
+
)
|
|
842
|
+
|
|
843
|
+
ra = check_output("ra", ra, shape=az.shape, dtype=np.double)
|
|
844
|
+
dec = check_output("dec", dec, shape=az.shape, dtype=np.double)
|
|
845
|
+
if return_pa:
|
|
846
|
+
pa = check_output("pa", pa, shape=az.shape, dtype=np.double)
|
|
847
|
+
else:
|
|
848
|
+
sin2psi = check_output("sin2psi", sin2psi, shape=az.shape, dtype=np.double)
|
|
849
|
+
cos2psi = check_output("cos2psi", cos2psi, shape=az.shape, dtype=np.double)
|
|
850
|
+
n = az.size
|
|
851
|
+
|
|
852
|
+
if hwp is None:
|
|
853
|
+
if return_pa:
|
|
854
|
+
qp.qp_azel2radecpa(
|
|
855
|
+
self._memory,
|
|
856
|
+
delta_az,
|
|
857
|
+
delta_el,
|
|
858
|
+
delta_psi,
|
|
859
|
+
az,
|
|
860
|
+
el,
|
|
861
|
+
pitch,
|
|
862
|
+
roll,
|
|
863
|
+
lon,
|
|
864
|
+
lat,
|
|
865
|
+
ctime,
|
|
866
|
+
ra,
|
|
867
|
+
dec,
|
|
868
|
+
pa,
|
|
869
|
+
n,
|
|
870
|
+
)
|
|
871
|
+
elif sindec:
|
|
872
|
+
qp.qp_azel2rasindec(
|
|
873
|
+
self._memory,
|
|
874
|
+
delta_az,
|
|
875
|
+
delta_el,
|
|
876
|
+
delta_psi,
|
|
877
|
+
az,
|
|
878
|
+
el,
|
|
879
|
+
pitch,
|
|
880
|
+
roll,
|
|
881
|
+
lon,
|
|
882
|
+
lat,
|
|
883
|
+
ctime,
|
|
884
|
+
ra,
|
|
885
|
+
dec,
|
|
886
|
+
sin2psi,
|
|
887
|
+
cos2psi,
|
|
888
|
+
n,
|
|
889
|
+
)
|
|
890
|
+
else:
|
|
891
|
+
qp.qp_azel2radec(
|
|
892
|
+
self._memory,
|
|
893
|
+
delta_az,
|
|
894
|
+
delta_el,
|
|
895
|
+
delta_psi,
|
|
896
|
+
az,
|
|
897
|
+
el,
|
|
898
|
+
pitch,
|
|
899
|
+
roll,
|
|
900
|
+
lon,
|
|
901
|
+
lat,
|
|
902
|
+
ctime,
|
|
903
|
+
ra,
|
|
904
|
+
dec,
|
|
905
|
+
sin2psi,
|
|
906
|
+
cos2psi,
|
|
907
|
+
n,
|
|
908
|
+
)
|
|
909
|
+
else:
|
|
910
|
+
hwp = check_input("hwp", hwp, shape=az.shape)
|
|
911
|
+
|
|
912
|
+
if return_pa:
|
|
913
|
+
qp.qp_azel2radec_hwp(
|
|
914
|
+
self._memory,
|
|
915
|
+
delta_az,
|
|
916
|
+
delta_el,
|
|
917
|
+
delta_psi,
|
|
918
|
+
az,
|
|
919
|
+
el,
|
|
920
|
+
pitch,
|
|
921
|
+
roll,
|
|
922
|
+
lon,
|
|
923
|
+
lat,
|
|
924
|
+
ctime,
|
|
925
|
+
hwp,
|
|
926
|
+
ra,
|
|
927
|
+
dec,
|
|
928
|
+
pa,
|
|
929
|
+
n,
|
|
930
|
+
)
|
|
931
|
+
elif sindec:
|
|
932
|
+
qp.qp_azel2rasindec_hwp(
|
|
933
|
+
self._memory,
|
|
934
|
+
delta_az,
|
|
935
|
+
delta_el,
|
|
936
|
+
delta_psi,
|
|
937
|
+
az,
|
|
938
|
+
el,
|
|
939
|
+
pitch,
|
|
940
|
+
roll,
|
|
941
|
+
lon,
|
|
942
|
+
lat,
|
|
943
|
+
ctime,
|
|
944
|
+
hwp,
|
|
945
|
+
ra,
|
|
946
|
+
dec,
|
|
947
|
+
sin2psi,
|
|
948
|
+
cos2psi,
|
|
949
|
+
n,
|
|
950
|
+
)
|
|
951
|
+
else:
|
|
952
|
+
qp.qp_azel2radec_hwp(
|
|
953
|
+
self._memory,
|
|
954
|
+
delta_az,
|
|
955
|
+
delta_el,
|
|
956
|
+
delta_psi,
|
|
957
|
+
az,
|
|
958
|
+
el,
|
|
959
|
+
pitch,
|
|
960
|
+
roll,
|
|
961
|
+
lon,
|
|
962
|
+
lat,
|
|
963
|
+
ctime,
|
|
964
|
+
hwp,
|
|
965
|
+
ra,
|
|
966
|
+
dec,
|
|
967
|
+
sin2psi,
|
|
968
|
+
cos2psi,
|
|
969
|
+
n,
|
|
970
|
+
)
|
|
971
|
+
|
|
972
|
+
if return_pa:
|
|
973
|
+
return ra, dec, pa
|
|
974
|
+
return ra, dec, sin2psi, cos2psi
|
|
975
|
+
|
|
976
|
+
def azelpsi2radec(
|
|
977
|
+
self,
|
|
978
|
+
delta_az,
|
|
979
|
+
delta_el,
|
|
980
|
+
delta_psi,
|
|
981
|
+
az,
|
|
982
|
+
el,
|
|
983
|
+
psi,
|
|
984
|
+
pitch,
|
|
985
|
+
roll,
|
|
986
|
+
lon,
|
|
987
|
+
lat,
|
|
988
|
+
ctime,
|
|
989
|
+
hwp=None,
|
|
990
|
+
sindec=False,
|
|
991
|
+
return_pa=False,
|
|
992
|
+
ra=None,
|
|
993
|
+
dec=None,
|
|
994
|
+
pa=None,
|
|
995
|
+
sin2psi=None,
|
|
996
|
+
cos2psi=None,
|
|
997
|
+
**kwargs,
|
|
998
|
+
):
|
|
999
|
+
"""
|
|
1000
|
+
Estimate the orientation on the sky for a detector offset from
|
|
1001
|
+
boresight, given the boresight attitude (az/el/pitch/roll), location on
|
|
1002
|
+
the earth (lon/lat) and UTC time. Input vectors must be
|
|
1003
|
+
numpy-array-like and broadcastable to the same shape. Detector offsets
|
|
1004
|
+
are defined assuming the boresight is pointed toward the horizon, and
|
|
1005
|
+
that the boresight polarization axis is along the horizontal.
|
|
1006
|
+
|
|
1007
|
+
This is agumented from azel2radec(), to accept rotations of the focal
|
|
1008
|
+
plane about the optical axis.
|
|
1009
|
+
|
|
1010
|
+
Arguments
|
|
1011
|
+
---------
|
|
1012
|
+
delta_az : float
|
|
1013
|
+
Azimuthal offset of the detector in degrees
|
|
1014
|
+
delta_el : float
|
|
1015
|
+
Elevation offset of the detector in degrees
|
|
1016
|
+
delta_psi : float
|
|
1017
|
+
Polarization offset of the detector in degrees
|
|
1018
|
+
az : array_like
|
|
1019
|
+
Boresight azimuth in degrees
|
|
1020
|
+
el : array_like
|
|
1021
|
+
Boresight elevation in degrees
|
|
1022
|
+
psi : array_like
|
|
1023
|
+
Boresight rotation in degrees
|
|
1024
|
+
pitch : array_like
|
|
1025
|
+
Boresight pitch in degrees. If None, this term is ignored.
|
|
1026
|
+
roll : array_like
|
|
1027
|
+
Boresight roll in degrees. If None, this term is ignored.
|
|
1028
|
+
lon : array_like
|
|
1029
|
+
Observer longitude in degrees.
|
|
1030
|
+
lat : array_like
|
|
1031
|
+
Observer latitude in degrees.
|
|
1032
|
+
ctime : array_like
|
|
1033
|
+
Unix time in seconds UTC
|
|
1034
|
+
hwp : array_like, optional
|
|
1035
|
+
HWP angles in degrees
|
|
1036
|
+
sindec : bool, optional
|
|
1037
|
+
If `True`, return sin(dec) instead of dec in degrees (default False)
|
|
1038
|
+
return_pa : bool, optional
|
|
1039
|
+
If `True`, return pa instead of sin2psi/cos2psi
|
|
1040
|
+
|
|
1041
|
+
Returns
|
|
1042
|
+
-------
|
|
1043
|
+
ra : array_like
|
|
1044
|
+
Detector right ascension in degrees
|
|
1045
|
+
dec/sindec : array_like
|
|
1046
|
+
Detector declination in degrees
|
|
1047
|
+
pa : array_like
|
|
1048
|
+
Detector position angle, if `return_pa` is True
|
|
1049
|
+
sin2psi : array_like
|
|
1050
|
+
Detector polarization orientation, if `return_pa` is False
|
|
1051
|
+
cos2psi : array_like
|
|
1052
|
+
Detector polarization orientation, if `return_pa` is False
|
|
1053
|
+
|
|
1054
|
+
Notes
|
|
1055
|
+
-----
|
|
1056
|
+
Any keywords accepted by the :meth:`set` method can also be passed here,
|
|
1057
|
+
and will be processed prior to calculation.
|
|
1058
|
+
"""
|
|
1059
|
+
|
|
1060
|
+
self.set(**kwargs)
|
|
1061
|
+
|
|
1062
|
+
az, el, psi, pitch, roll, lon, lat, ctime = check_inputs(
|
|
1063
|
+
az, el, psi, pitch, roll, lon, lat, ctime
|
|
1064
|
+
)
|
|
1065
|
+
|
|
1066
|
+
ra = check_output("ra", ra, shape=az.shape, dtype=np.double)
|
|
1067
|
+
dec = check_output("dec", dec, shape=az.shape, dtype=np.double)
|
|
1068
|
+
if return_pa:
|
|
1069
|
+
pa = check_output("pa", pa, shape=az.shape, dtype=np.double)
|
|
1070
|
+
else:
|
|
1071
|
+
sin2psi = check_output("sin2psi", sin2psi, shape=az.shape, dtype=np.double)
|
|
1072
|
+
cos2psi = check_output("cos2psi", cos2psi, shape=az.shape, dtype=np.double)
|
|
1073
|
+
n = az.size
|
|
1074
|
+
|
|
1075
|
+
if hwp is None:
|
|
1076
|
+
if return_pa:
|
|
1077
|
+
qp.qp_azelpsi2radecpa(
|
|
1078
|
+
self._memory,
|
|
1079
|
+
delta_az,
|
|
1080
|
+
delta_el,
|
|
1081
|
+
delta_psi,
|
|
1082
|
+
az,
|
|
1083
|
+
el,
|
|
1084
|
+
psi,
|
|
1085
|
+
pitch,
|
|
1086
|
+
roll,
|
|
1087
|
+
lon,
|
|
1088
|
+
lat,
|
|
1089
|
+
ctime,
|
|
1090
|
+
ra,
|
|
1091
|
+
dec,
|
|
1092
|
+
pa,
|
|
1093
|
+
n,
|
|
1094
|
+
)
|
|
1095
|
+
elif sindec:
|
|
1096
|
+
qp.qp_azelpsi2rasindec(
|
|
1097
|
+
self._memory,
|
|
1098
|
+
delta_az,
|
|
1099
|
+
delta_el,
|
|
1100
|
+
delta_psi,
|
|
1101
|
+
az,
|
|
1102
|
+
el,
|
|
1103
|
+
psi,
|
|
1104
|
+
pitch,
|
|
1105
|
+
roll,
|
|
1106
|
+
lon,
|
|
1107
|
+
lat,
|
|
1108
|
+
ctime,
|
|
1109
|
+
ra,
|
|
1110
|
+
dec,
|
|
1111
|
+
sin2psi,
|
|
1112
|
+
cos2psi,
|
|
1113
|
+
n,
|
|
1114
|
+
)
|
|
1115
|
+
else:
|
|
1116
|
+
qp.qp_azelpsi2radec(
|
|
1117
|
+
self._memory,
|
|
1118
|
+
delta_az,
|
|
1119
|
+
delta_el,
|
|
1120
|
+
delta_psi,
|
|
1121
|
+
az,
|
|
1122
|
+
el,
|
|
1123
|
+
psi,
|
|
1124
|
+
pitch,
|
|
1125
|
+
roll,
|
|
1126
|
+
lon,
|
|
1127
|
+
lat,
|
|
1128
|
+
ctime,
|
|
1129
|
+
ra,
|
|
1130
|
+
dec,
|
|
1131
|
+
sin2psi,
|
|
1132
|
+
cos2psi,
|
|
1133
|
+
n,
|
|
1134
|
+
)
|
|
1135
|
+
else:
|
|
1136
|
+
hwp = check_input("hwp", hwp, shape=az.shape)
|
|
1137
|
+
|
|
1138
|
+
if return_pa:
|
|
1139
|
+
qp.qp_azelpsi2radec_hwp(
|
|
1140
|
+
self._memory,
|
|
1141
|
+
delta_az,
|
|
1142
|
+
delta_el,
|
|
1143
|
+
delta_psi,
|
|
1144
|
+
az,
|
|
1145
|
+
el,
|
|
1146
|
+
psi,
|
|
1147
|
+
pitch,
|
|
1148
|
+
roll,
|
|
1149
|
+
lon,
|
|
1150
|
+
lat,
|
|
1151
|
+
ctime,
|
|
1152
|
+
hwp,
|
|
1153
|
+
ra,
|
|
1154
|
+
dec,
|
|
1155
|
+
pa,
|
|
1156
|
+
n,
|
|
1157
|
+
)
|
|
1158
|
+
elif sindec:
|
|
1159
|
+
qp.qp_azelpsi2rasindec_hwp(
|
|
1160
|
+
self._memory,
|
|
1161
|
+
delta_az,
|
|
1162
|
+
delta_el,
|
|
1163
|
+
delta_psi,
|
|
1164
|
+
az,
|
|
1165
|
+
el,
|
|
1166
|
+
psi,
|
|
1167
|
+
pitch,
|
|
1168
|
+
roll,
|
|
1169
|
+
lon,
|
|
1170
|
+
lat,
|
|
1171
|
+
ctime,
|
|
1172
|
+
hwp,
|
|
1173
|
+
ra,
|
|
1174
|
+
dec,
|
|
1175
|
+
sin2psi,
|
|
1176
|
+
cos2psi,
|
|
1177
|
+
n,
|
|
1178
|
+
)
|
|
1179
|
+
else:
|
|
1180
|
+
qp.qp_azelpsi2radec_hwp(
|
|
1181
|
+
self._memory,
|
|
1182
|
+
delta_az,
|
|
1183
|
+
delta_el,
|
|
1184
|
+
delta_psi,
|
|
1185
|
+
az,
|
|
1186
|
+
el,
|
|
1187
|
+
psi,
|
|
1188
|
+
pitch,
|
|
1189
|
+
roll,
|
|
1190
|
+
lon,
|
|
1191
|
+
lat,
|
|
1192
|
+
ctime,
|
|
1193
|
+
hwp,
|
|
1194
|
+
ra,
|
|
1195
|
+
dec,
|
|
1196
|
+
sin2psi,
|
|
1197
|
+
cos2psi,
|
|
1198
|
+
n,
|
|
1199
|
+
)
|
|
1200
|
+
|
|
1201
|
+
if return_pa:
|
|
1202
|
+
return ra, dec, pa
|
|
1203
|
+
return ra, dec, sin2psi, cos2psi
|
|
1204
|
+
|
|
1205
|
+
def radec2azel(
|
|
1206
|
+
self, ra, dec, pa, lon, lat, ctime, az=None, el=None, hpa=None, **kwargs
|
|
1207
|
+
):
|
|
1208
|
+
"""
|
|
1209
|
+
Estimate the horizon coordinates for a given set of equatorial coordinates
|
|
1210
|
+
(ra/dec/psi), location on the earth (lon/lat) and UTC time. Input vectors
|
|
1211
|
+
must be numpy-array-like and broadcastable to the same shape.
|
|
1212
|
+
|
|
1213
|
+
Arguments
|
|
1214
|
+
---------
|
|
1215
|
+
ra : array_like
|
|
1216
|
+
Right ascension angle
|
|
1217
|
+
dec : array_like
|
|
1218
|
+
Declination angle
|
|
1219
|
+
pa : array_like
|
|
1220
|
+
Position angle in equatorial coordinates
|
|
1221
|
+
lon : array_like
|
|
1222
|
+
Observer longitude in degrees.
|
|
1223
|
+
lat : array_like
|
|
1224
|
+
Observer latitude in degrees.
|
|
1225
|
+
ctime : array_like
|
|
1226
|
+
Unix time in seconds UTC
|
|
1227
|
+
|
|
1228
|
+
Returns
|
|
1229
|
+
-------
|
|
1230
|
+
az : array_like
|
|
1231
|
+
Azimuth in degrees
|
|
1232
|
+
el : array_like
|
|
1233
|
+
Elevation in degrees
|
|
1234
|
+
hpa : array_like
|
|
1235
|
+
Position angle in horizon coordinates
|
|
1236
|
+
|
|
1237
|
+
Notes
|
|
1238
|
+
-----
|
|
1239
|
+
Any keywords accepted by the :meth:`set` method can also be passed here,
|
|
1240
|
+
and will be processed prior to calculation.
|
|
1241
|
+
"""
|
|
1242
|
+
|
|
1243
|
+
self.set(**kwargs)
|
|
1244
|
+
|
|
1245
|
+
ra, dec, pa, lon, lat, ctime = check_inputs(ra, dec, pa, lon, lat, ctime)
|
|
1246
|
+
|
|
1247
|
+
az = check_output("az", az, shape=ra.shape, dtype=np.double)
|
|
1248
|
+
el = check_output("el", el, shape=ra.shape, dtype=np.double)
|
|
1249
|
+
hpa = check_output("hpa", hpa, shape=ra.shape, dtype=np.double)
|
|
1250
|
+
n = ra.size
|
|
1251
|
+
|
|
1252
|
+
qp.qp_radec2azel(self._memory, ra, dec, pa, lon, lat, ctime, az, el, hpa, n)
|
|
1253
|
+
|
|
1254
|
+
return az, el, hpa
|
|
1255
|
+
|
|
1256
|
+
def radecpa2quat(self, ra, dec, pa, **kwargs):
|
|
1257
|
+
"""
|
|
1258
|
+
Calculate quaternion for input ra/dec/pa. Vectorized, input arguments
|
|
1259
|
+
must be broadcastable to the same shape.
|
|
1260
|
+
|
|
1261
|
+
Arguments
|
|
1262
|
+
---------
|
|
1263
|
+
ra : array_like
|
|
1264
|
+
Right ascension angle
|
|
1265
|
+
dec : array_like
|
|
1266
|
+
Declination angle
|
|
1267
|
+
pa : array_like
|
|
1268
|
+
Position angle
|
|
1269
|
+
|
|
1270
|
+
Returns
|
|
1271
|
+
-------
|
|
1272
|
+
q : array_like
|
|
1273
|
+
Quaternion constructed from the input angles.
|
|
1274
|
+
|
|
1275
|
+
Notes
|
|
1276
|
+
-----
|
|
1277
|
+
Any keywords accepted by the :meth:`set` method can also be passed here,
|
|
1278
|
+
and will be processed prior to calculation.
|
|
1279
|
+
"""
|
|
1280
|
+
self.set(**kwargs)
|
|
1281
|
+
|
|
1282
|
+
ra, dec, pa = check_inputs(ra, dec, pa)
|
|
1283
|
+
n = ra.size
|
|
1284
|
+
quat = check_output("quat", shape=(n, 4), dtype=np.double, **kwargs)
|
|
1285
|
+
qp.qp_radecpa2quatn(self._memory, ra, dec, pa, quat, n)
|
|
1286
|
+
|
|
1287
|
+
if n == 1:
|
|
1288
|
+
return quat[0]
|
|
1289
|
+
return quat
|
|
1290
|
+
|
|
1291
|
+
def quat2radecpa(self, quat, **kwargs):
|
|
1292
|
+
"""
|
|
1293
|
+
Calculate ra/dec/pa for input quaternion(s).
|
|
1294
|
+
|
|
1295
|
+
Arguments
|
|
1296
|
+
---------
|
|
1297
|
+
q : array_like
|
|
1298
|
+
Pointing quaternion
|
|
1299
|
+
|
|
1300
|
+
Returns
|
|
1301
|
+
-------
|
|
1302
|
+
ra : array_like
|
|
1303
|
+
Right ascension angle
|
|
1304
|
+
dec : array_like
|
|
1305
|
+
Declination angle
|
|
1306
|
+
pa : array_like
|
|
1307
|
+
Position angle
|
|
1308
|
+
|
|
1309
|
+
Notes
|
|
1310
|
+
-----
|
|
1311
|
+
Any keywords accepted by the :meth:`set` method can also be passed here,
|
|
1312
|
+
and will be processed prior to calculation.
|
|
1313
|
+
"""
|
|
1314
|
+
self.set(**kwargs)
|
|
1315
|
+
|
|
1316
|
+
quat = check_input("quat", np.atleast_2d(quat), quat=True)
|
|
1317
|
+
n = quat.shape[0]
|
|
1318
|
+
ra = check_output("ra", shape=(n,), dtype=np.double, **kwargs)
|
|
1319
|
+
dec = check_output("dec", shape=(n,), dtype=np.double, **kwargs)
|
|
1320
|
+
pa = check_output("pa", shape=(n,), dtype=np.double, **kwargs)
|
|
1321
|
+
|
|
1322
|
+
qp.qp_quat2radecpan(self._memory, quat, ra, dec, pa, n)
|
|
1323
|
+
if n == 1:
|
|
1324
|
+
return ra[0], dec[0], pa[0]
|
|
1325
|
+
return ra, dec, pa
|
|
1326
|
+
|
|
1327
|
+
def quat2pixpa(self, quat, nside=256, **kwargs):
|
|
1328
|
+
"""
|
|
1329
|
+
Calculate ra/dec/pa for input quaternion(s).
|
|
1330
|
+
|
|
1331
|
+
Arguments
|
|
1332
|
+
---------
|
|
1333
|
+
q : array_like
|
|
1334
|
+
Pointing quaternion
|
|
1335
|
+
|
|
1336
|
+
Returns
|
|
1337
|
+
-------
|
|
1338
|
+
pix : array_like
|
|
1339
|
+
Pixel number(s) corresponding to the input positions(s).
|
|
1340
|
+
pa : array_like
|
|
1341
|
+
Position angle
|
|
1342
|
+
|
|
1343
|
+
Notes
|
|
1344
|
+
-----
|
|
1345
|
+
Any keywords accepted by the :meth:`set` method can also be passed here,
|
|
1346
|
+
and will be processed prior to calculation.
|
|
1347
|
+
"""
|
|
1348
|
+
self.set(**kwargs)
|
|
1349
|
+
|
|
1350
|
+
quat = check_input("quat", np.atleast_2d(quat), quat=True)
|
|
1351
|
+
n = quat.shape[0]
|
|
1352
|
+
pix = check_output("pix", shape=(n,), dtype=int, **kwargs)
|
|
1353
|
+
pa = check_output("pa", shape=(n,), dtype=np.double, **kwargs)
|
|
1354
|
+
|
|
1355
|
+
qp.qp_quat2pixpan(self._memory, quat, pix, pa, n)
|
|
1356
|
+
if n == 1:
|
|
1357
|
+
return pix[0], pa[0]
|
|
1358
|
+
return pix, pa
|
|
1359
|
+
|
|
1360
|
+
def radec2pix(self, ra, dec, nside=256, **kwargs):
|
|
1361
|
+
"""
|
|
1362
|
+
Calculate HEALpix pixel number for given ra/dec and nside. Vectorized,
|
|
1363
|
+
input arguments must be broadcastable to the same shape.
|
|
1364
|
+
|
|
1365
|
+
Arguments
|
|
1366
|
+
---------
|
|
1367
|
+
ra : array_like
|
|
1368
|
+
Right ascension angle
|
|
1369
|
+
dec : array_like
|
|
1370
|
+
Declination angle
|
|
1371
|
+
nside : int
|
|
1372
|
+
HEALpix resolution parameter
|
|
1373
|
+
|
|
1374
|
+
Returns
|
|
1375
|
+
-------
|
|
1376
|
+
pix : array_like
|
|
1377
|
+
Pixel number(s) corresponding to the input positions(s).
|
|
1378
|
+
|
|
1379
|
+
Notes
|
|
1380
|
+
-----
|
|
1381
|
+
Any keywords accepted by the :meth:`set` method can also be passed here,
|
|
1382
|
+
and will be processed prior to calculation.
|
|
1383
|
+
"""
|
|
1384
|
+
|
|
1385
|
+
self.set(**kwargs)
|
|
1386
|
+
|
|
1387
|
+
ra, dec = check_inputs(ra, dec)
|
|
1388
|
+
n = ra.size
|
|
1389
|
+
|
|
1390
|
+
pix = check_output("pix", shape=ra.shape, dtype=int, **kwargs)
|
|
1391
|
+
qp.qp_radec2pixn(self._memory, ra, dec, nside, pix, n)
|
|
1392
|
+
|
|
1393
|
+
if n == 1:
|
|
1394
|
+
return pix[0]
|
|
1395
|
+
return pix
|
|
1396
|
+
|
|
1397
|
+
def rotate_quat(self, quat, coord=["C", "G"], inplace=True, **kwargs):
|
|
1398
|
+
"""
|
|
1399
|
+
Rotate a quaternion from one coordinate system to another.
|
|
1400
|
+
Supported coordinates:
|
|
1401
|
+
|
|
1402
|
+
C: celestial (equatorial) coordinates
|
|
1403
|
+
G: galactic coordinates
|
|
1404
|
+
|
|
1405
|
+
Arguments
|
|
1406
|
+
---------
|
|
1407
|
+
quat : array_like
|
|
1408
|
+
array of quaternions, of shape (n, 4)
|
|
1409
|
+
coord : list, optional
|
|
1410
|
+
2-element list of input and output coordinates
|
|
1411
|
+
inplace : bool, optional
|
|
1412
|
+
If True, apply the rotation in-place on the input quaternion.
|
|
1413
|
+
Otherwise, return a copy of the input array. Default: True.
|
|
1414
|
+
|
|
1415
|
+
Returns
|
|
1416
|
+
-------
|
|
1417
|
+
quat : array_like
|
|
1418
|
+
rotated quaternion array
|
|
1419
|
+
|
|
1420
|
+
Notes
|
|
1421
|
+
-----
|
|
1422
|
+
Any keywords accepted by the :meth:`set` method can also be passed here,
|
|
1423
|
+
and will be processed prior to calculation.
|
|
1424
|
+
"""
|
|
1425
|
+
|
|
1426
|
+
self.set(**kwargs)
|
|
1427
|
+
|
|
1428
|
+
quat = check_input(
|
|
1429
|
+
"quat", np.atleast_2d(quat), quat=True, inplace=inplace, output=True
|
|
1430
|
+
)
|
|
1431
|
+
n = quat.size // 4
|
|
1432
|
+
|
|
1433
|
+
if coord[0] == "C" and coord[1] == "G":
|
|
1434
|
+
qp.qp_radec2gal_quatn(self._memory, quat, n)
|
|
1435
|
+
elif coord[0] == "G" and coord[1] == "C":
|
|
1436
|
+
qp.qp_gal2radec_quatn(self._memory, quat, n)
|
|
1437
|
+
else:
|
|
1438
|
+
raise ValueError("unsupported coord {}".format(coord))
|
|
1439
|
+
|
|
1440
|
+
return quat.squeeze()
|
|
1441
|
+
|
|
1442
|
+
def rotate_coord(
|
|
1443
|
+
self,
|
|
1444
|
+
ra,
|
|
1445
|
+
dec,
|
|
1446
|
+
pa=None,
|
|
1447
|
+
sin2psi=None,
|
|
1448
|
+
cos2psi=None,
|
|
1449
|
+
coord=["C", "G"],
|
|
1450
|
+
inplace=True,
|
|
1451
|
+
**kwargs,
|
|
1452
|
+
):
|
|
1453
|
+
"""
|
|
1454
|
+
Rotate coordinates from one coordinate system to another. Vectorized,
|
|
1455
|
+
input arguments must be broadcastable to the same shape.
|
|
1456
|
+
Supported coordinates:
|
|
1457
|
+
|
|
1458
|
+
C: celestial (equatorial) coordinates
|
|
1459
|
+
G: galactic coordinates
|
|
1460
|
+
|
|
1461
|
+
Arguments
|
|
1462
|
+
---------
|
|
1463
|
+
ra, dec, pa : array_like
|
|
1464
|
+
-- or --
|
|
1465
|
+
ra, dec, sin2psi, cos2psi : array_like
|
|
1466
|
+
arrays of coordinates, of shape (n,)
|
|
1467
|
+
If none of pa, sin2psi or cos2psi are supplied,
|
|
1468
|
+
pa = 0 is assumed.
|
|
1469
|
+
coord : list, optional
|
|
1470
|
+
2-element list of input and output coordinates.
|
|
1471
|
+
Supported systems: C, G.
|
|
1472
|
+
inplace : bool, optional
|
|
1473
|
+
If True, apply the rotation in-place on the input coordinates.
|
|
1474
|
+
Otherwise, return a copy of the input array. Default: True.
|
|
1475
|
+
|
|
1476
|
+
Returns
|
|
1477
|
+
-------
|
|
1478
|
+
ra, dec, pa : array_like
|
|
1479
|
+
-- or --
|
|
1480
|
+
ra, dec, sin2psi, cos2psi : array_like
|
|
1481
|
+
rotated coordinate arrays, same form as input
|
|
1482
|
+
|
|
1483
|
+
Notes
|
|
1484
|
+
-----
|
|
1485
|
+
Any keywords accepted by the :meth:`set` method can also be passed here,
|
|
1486
|
+
and will be processed prior to calculation.
|
|
1487
|
+
"""
|
|
1488
|
+
self.set(**kwargs)
|
|
1489
|
+
|
|
1490
|
+
do_pa = True
|
|
1491
|
+
if pa is None:
|
|
1492
|
+
if sin2psi is None and cos2psi is None:
|
|
1493
|
+
pass
|
|
1494
|
+
elif sin2psi is None or cos2psi is None:
|
|
1495
|
+
raise KeyError("both sin2psi and cos2psi arguments required")
|
|
1496
|
+
else:
|
|
1497
|
+
do_pa = False
|
|
1498
|
+
else:
|
|
1499
|
+
if sin2psi is not None or cos2psi is not None:
|
|
1500
|
+
raise KeyError(
|
|
1501
|
+
"ambiguous pol arguments, supply either pa "
|
|
1502
|
+
"or sin2psi/cos2psi only"
|
|
1503
|
+
)
|
|
1504
|
+
|
|
1505
|
+
ra, dec, pa, sin2psi, cos2psi = check_inputs(
|
|
1506
|
+
ra, dec, pa, sin2psi, cos2psi, inplace=inplace, output=True
|
|
1507
|
+
)
|
|
1508
|
+
n = ra.size
|
|
1509
|
+
|
|
1510
|
+
if coord[0] == "C" and coord[1] == "G":
|
|
1511
|
+
if do_pa:
|
|
1512
|
+
qp.qp_radecpa2galn(self._memory, ra, dec, pa, n)
|
|
1513
|
+
else:
|
|
1514
|
+
qp.qp_radec2galn(self._memory, ra, dec, sin2psi, cos2psi, n)
|
|
1515
|
+
elif coord[0] == "G" and coord[1] == "C":
|
|
1516
|
+
if do_pa:
|
|
1517
|
+
qp.qp_gal2radecpan(self._memory, ra, dec, pa, n)
|
|
1518
|
+
else:
|
|
1519
|
+
qp.qp_gal2radecn(self._memory, ra, dec, sin2psi, cos2psi, n)
|
|
1520
|
+
|
|
1521
|
+
if n == 1:
|
|
1522
|
+
if do_pa:
|
|
1523
|
+
return ra[0], dec[0], pa[0]
|
|
1524
|
+
return ra[0], dec[0], sin2psi[0], cos2psi[0]
|
|
1525
|
+
if do_pa:
|
|
1526
|
+
return ra, dec, pa
|
|
1527
|
+
return ra, dec, sin2psi, cos2psi
|
|
1528
|
+
|
|
1529
|
+
def radec2gal(
|
|
1530
|
+
self, ra, dec, pa=None, sin2psi=None, cos2psi=None, inplace=True, **kwargs
|
|
1531
|
+
):
|
|
1532
|
+
"""
|
|
1533
|
+
Rotate celestial (equatorial) coordinates to galactic coordinates. This
|
|
1534
|
+
is equivalent to calling :meth:`rotate_coord` with `coord=['C', 'G']`.
|
|
1535
|
+
Vectorized, input arguments must be broadcastable to the same shape.
|
|
1536
|
+
|
|
1537
|
+
Arguments
|
|
1538
|
+
---------
|
|
1539
|
+
ra, dec, pa : array_like
|
|
1540
|
+
-- or --
|
|
1541
|
+
ra, dec, sin2psi, cos2psi : array_like
|
|
1542
|
+
arrays of coordinates, of shape (n,). If none of pa, sin2psi or
|
|
1543
|
+
cos2psi are supplied, pa = 0 is assumed.
|
|
1544
|
+
inplace : bool, optional
|
|
1545
|
+
If True, apply the rotation in-place on the input quaternion.
|
|
1546
|
+
Otherwise, return a copy of the input array. Default: True.
|
|
1547
|
+
|
|
1548
|
+
Returns
|
|
1549
|
+
-------
|
|
1550
|
+
l, b, pa : array_like
|
|
1551
|
+
-- or --
|
|
1552
|
+
l, b, sin2psi, cos2psi : array_like
|
|
1553
|
+
rotated coordinate arrays, same form as input
|
|
1554
|
+
|
|
1555
|
+
Notes
|
|
1556
|
+
-----
|
|
1557
|
+
Any keywords accepted by the :meth:`set` method can also be passed here,
|
|
1558
|
+
and will be processed prior to calculation.
|
|
1559
|
+
"""
|
|
1560
|
+
return self.rotate_coord(
|
|
1561
|
+
ra, dec, pa, sin2psi, cos2psi, coord=["C", "G"], inplace=inplace, **kwargs
|
|
1562
|
+
)
|
|
1563
|
+
|
|
1564
|
+
def gal2radec(
|
|
1565
|
+
self, ra, dec, pa=None, sin2psi=None, cos2psi=None, inplace=True, **kwargs
|
|
1566
|
+
):
|
|
1567
|
+
"""
|
|
1568
|
+
Rotate galactic coordinates to celestial (equatorial) coordinates. This
|
|
1569
|
+
is equivalent to calling :meth:`rotate_coord` with `coord=['G', 'C']`.
|
|
1570
|
+
Vectorized, input arguments must be broadcastable to the same shape.
|
|
1571
|
+
|
|
1572
|
+
Arguments
|
|
1573
|
+
---------
|
|
1574
|
+
l, b, pa : array_like
|
|
1575
|
+
-- or --
|
|
1576
|
+
l, b, sin2psi, cos2psi : array_like
|
|
1577
|
+
arrays of coordinates, of shape (n,). If none of pa, sin2psi or
|
|
1578
|
+
cos2psi are supplied, pa = 0 is assumed.
|
|
1579
|
+
inplace : bool, optional
|
|
1580
|
+
If True, apply the rotation in-place on the input quaternion.
|
|
1581
|
+
Otherwise, return a copy of the input array. Default: True.
|
|
1582
|
+
|
|
1583
|
+
Returns
|
|
1584
|
+
-------
|
|
1585
|
+
ra, dec, pa : array_like
|
|
1586
|
+
-- or --
|
|
1587
|
+
ra, dec, sin2psi, cos2psi : array_like
|
|
1588
|
+
rotated coordinate arrays, same form as input
|
|
1589
|
+
|
|
1590
|
+
Notes
|
|
1591
|
+
-----
|
|
1592
|
+
Any keywords accepted by the :meth:`set` method can also be passed here,
|
|
1593
|
+
and will be processed prior to calculation.
|
|
1594
|
+
"""
|
|
1595
|
+
return self.rotate_coord(
|
|
1596
|
+
ra, dec, pa, sin2psi, cos2psi, coord=["G", "C"], inplace=inplace, **kwargs
|
|
1597
|
+
)
|
|
1598
|
+
|
|
1599
|
+
def rotate_map(
|
|
1600
|
+
self, map_in, coord=["C", "G"], map_out=None, interp_pix=True, **kwargs
|
|
1601
|
+
):
|
|
1602
|
+
"""
|
|
1603
|
+
Rotate a polarized 3-x-npix map from one coordinate system to another.
|
|
1604
|
+
Supported coordinates:
|
|
1605
|
+
|
|
1606
|
+
C = celestial (equatorial J2000)
|
|
1607
|
+
G = galactic
|
|
1608
|
+
|
|
1609
|
+
Arguments
|
|
1610
|
+
---------
|
|
1611
|
+
map_in : array_like
|
|
1612
|
+
Input map, of shape (3, N)
|
|
1613
|
+
coord : list, optional
|
|
1614
|
+
2-element list of input and output coordinates.
|
|
1615
|
+
Supported systems: C, G.
|
|
1616
|
+
map_out : array_like, optional
|
|
1617
|
+
Rotated output map, for inplace operation. Same shape as `map_in`.
|
|
1618
|
+
interp_pix : bool, optional
|
|
1619
|
+
If True, interpolate the rotated map.
|
|
1620
|
+
|
|
1621
|
+
Returns
|
|
1622
|
+
-------
|
|
1623
|
+
map_out : array_like
|
|
1624
|
+
Rotated output map.
|
|
1625
|
+
|
|
1626
|
+
Notes
|
|
1627
|
+
-----
|
|
1628
|
+
Any keywords accepted by the :meth:`set` method can also be passed here,
|
|
1629
|
+
and will be processed prior to calculation.
|
|
1630
|
+
|
|
1631
|
+
Only full-sky maps are currently supported.
|
|
1632
|
+
"""
|
|
1633
|
+
|
|
1634
|
+
warn("This code is buggy, use at your own risk", UserWarning)
|
|
1635
|
+
|
|
1636
|
+
interp_orig = self.get("interp_pix")
|
|
1637
|
+
self.set(interp_pix=interp_pix, **kwargs)
|
|
1638
|
+
|
|
1639
|
+
from .qmap_class import check_map
|
|
1640
|
+
|
|
1641
|
+
map_in, nside = check_map(map_in)
|
|
1642
|
+
map_out = check_output(
|
|
1643
|
+
"map_out", map_out, shape=map_in.shape, dtype=map_in.dtype, fill=0
|
|
1644
|
+
)
|
|
1645
|
+
|
|
1646
|
+
try:
|
|
1647
|
+
coord_in = coord[0]
|
|
1648
|
+
coord_out = coord[1]
|
|
1649
|
+
except:
|
|
1650
|
+
raise ValueError("unable to parse coord")
|
|
1651
|
+
|
|
1652
|
+
map_in_p = lib.pointer_2d(map_in)
|
|
1653
|
+
map_out_p = lib.pointer_2d(map_out)
|
|
1654
|
+
|
|
1655
|
+
qp.qp_rotate_map(self._memory, nside, map_in_p, coord_in, map_out_p, coord_out)
|
|
1656
|
+
|
|
1657
|
+
self.set(interp_pix=interp_orig)
|
|
1658
|
+
return map_out
|
|
1659
|
+
|
|
1660
|
+
def quat2pix(self, quat, nside=256, pol=True, **kwargs):
|
|
1661
|
+
"""
|
|
1662
|
+
Calculate HEALpix pixel number and optional polarization angle
|
|
1663
|
+
for a given orientation.
|
|
1664
|
+
|
|
1665
|
+
Arguments
|
|
1666
|
+
---------
|
|
1667
|
+
quat : quaternion or array of quaternions
|
|
1668
|
+
Pointing orientation(s)
|
|
1669
|
+
nside : int, optional
|
|
1670
|
+
HEALpix resolution parameter
|
|
1671
|
+
pol : bool, optional
|
|
1672
|
+
If True, return sin2psi and cos2psi along with the pixel number(s)
|
|
1673
|
+
|
|
1674
|
+
Returns
|
|
1675
|
+
-------
|
|
1676
|
+
pix : array_like
|
|
1677
|
+
Pixel number(s) for the given input quaternion(s)
|
|
1678
|
+
sin2psi : array_like
|
|
1679
|
+
cos2psi : array_like
|
|
1680
|
+
Polarization coefficients, if `pol` is `True`.
|
|
1681
|
+
|
|
1682
|
+
Notes
|
|
1683
|
+
-----
|
|
1684
|
+
Any keywords accepted by the :meth:`set` method can also be passed here,
|
|
1685
|
+
and will be processed prior to calculation.
|
|
1686
|
+
"""
|
|
1687
|
+
|
|
1688
|
+
self.set(**kwargs)
|
|
1689
|
+
|
|
1690
|
+
quat = check_input("quat", np.atleast_2d(quat), quat=True)
|
|
1691
|
+
|
|
1692
|
+
n = quat.shape[0]
|
|
1693
|
+
shape = (n,)
|
|
1694
|
+
pix = check_output("pix", shape=shape, dtype=int, **kwargs)
|
|
1695
|
+
sin2psi = check_output("sin2psi", shape=shape, **kwargs)
|
|
1696
|
+
cos2psi = check_output("cos2psi", shape=shape, **kwargs)
|
|
1697
|
+
qp.qp_quat2pixn(self._memory, quat, nside, pix, sin2psi, cos2psi, n)
|
|
1698
|
+
|
|
1699
|
+
if n == 1:
|
|
1700
|
+
pix, sin2psi, cos2psi = pix[0], sin2psi[0], cos2psi[0]
|
|
1701
|
+
if pol:
|
|
1702
|
+
return pix, sin2psi, cos2psi
|
|
1703
|
+
return pix
|
|
1704
|
+
|
|
1705
|
+
def bore2pix(
|
|
1706
|
+
self,
|
|
1707
|
+
q_off,
|
|
1708
|
+
ctime,
|
|
1709
|
+
q_bore,
|
|
1710
|
+
q_hwp=None,
|
|
1711
|
+
nside=256,
|
|
1712
|
+
pol=True,
|
|
1713
|
+
return_pa=False,
|
|
1714
|
+
**kwargs,
|
|
1715
|
+
):
|
|
1716
|
+
"""
|
|
1717
|
+
Calculate the orientation on the sky for a detector offset from the
|
|
1718
|
+
boresight. Detector offsets are defined assuming the boresight is
|
|
1719
|
+
pointed toward the horizon, and that the boresight polarization axis is
|
|
1720
|
+
along the vertical.
|
|
1721
|
+
|
|
1722
|
+
Arguments
|
|
1723
|
+
---------
|
|
1724
|
+
q_off : quaternion
|
|
1725
|
+
Detector offset quaternion for a single detector,
|
|
1726
|
+
calculated using :meth:`det_offset`.
|
|
1727
|
+
ctime : array_like
|
|
1728
|
+
Unix times in seconds UTC, broadcastable to shape (N,),
|
|
1729
|
+
the long dimenions of `q_bore`.
|
|
1730
|
+
q_bore : quaternion or array of quaternions
|
|
1731
|
+
Nx4 array of quaternions encoding the boresight orientation on the
|
|
1732
|
+
sky (as output by :meth:`azel2radec` or equivalent)
|
|
1733
|
+
q_hwp : quaternion or array of quaternions, optional
|
|
1734
|
+
HWP angle quaternions calculated using :meth:`hwp_quat`. Must be
|
|
1735
|
+
broadcastable to the same shape as `q_bore`.
|
|
1736
|
+
nside : int, optional
|
|
1737
|
+
HEALpix map dimension. Default: 256.
|
|
1738
|
+
pol : bool, optional
|
|
1739
|
+
If `False`, return only the pixel timestream
|
|
1740
|
+
return_pa : bool, optional
|
|
1741
|
+
If `True`, return pa instead of sin2psi / cos2psi
|
|
1742
|
+
|
|
1743
|
+
Returns
|
|
1744
|
+
-------
|
|
1745
|
+
pix : array_like
|
|
1746
|
+
Detector pixel number
|
|
1747
|
+
pa/sin2psi : array_like
|
|
1748
|
+
Detector polarization orientation if `return_pa` is `True`, or
|
|
1749
|
+
sin(2*pa) if `return_pa` is `False`.
|
|
1750
|
+
cos2psi : array_like
|
|
1751
|
+
detector polarization orientation cos(2*pa), if `return_pa` is `False`.
|
|
1752
|
+
|
|
1753
|
+
Notes
|
|
1754
|
+
-----
|
|
1755
|
+
Any keywords accepted by the :meth:`set` method can also be passed here,
|
|
1756
|
+
and will be processed prior to calculation.
|
|
1757
|
+
"""
|
|
1758
|
+
|
|
1759
|
+
self.set(**kwargs)
|
|
1760
|
+
|
|
1761
|
+
q_off = check_input("q_off", q_off, quat=True)
|
|
1762
|
+
q_bore = check_input("q_bore", q_bore, quat=True)
|
|
1763
|
+
if ctime is None:
|
|
1764
|
+
if not self.get("mean_aber"):
|
|
1765
|
+
raise ValueError("ctime required if mean_aber is False")
|
|
1766
|
+
ctime = np.zeros((q_bore.size // 4,), dtype=q_bore.dtype)
|
|
1767
|
+
ctime = check_input("ctime", ctime)
|
|
1768
|
+
pix = check_output("pix", shape=ctime.shape, dtype=int, **kwargs)
|
|
1769
|
+
if return_pa:
|
|
1770
|
+
pa = check_output("pa", shape=ctime.shape, **kwargs)
|
|
1771
|
+
else:
|
|
1772
|
+
sin2psi = check_output("sin2psi", shape=ctime.shape, **kwargs)
|
|
1773
|
+
cos2psi = check_output("cos2psi", shape=ctime.shape, **kwargs)
|
|
1774
|
+
n = ctime.size
|
|
1775
|
+
|
|
1776
|
+
if q_hwp is None:
|
|
1777
|
+
if return_pa:
|
|
1778
|
+
qp.qp_bore2pixpa(self._memory, q_off, ctime, q_bore, nside, pix, pa, n)
|
|
1779
|
+
else:
|
|
1780
|
+
qp.qp_bore2pix(
|
|
1781
|
+
self._memory, q_off, ctime, q_bore, nside, pix, sin2psi, cos2psi, n
|
|
1782
|
+
)
|
|
1783
|
+
else:
|
|
1784
|
+
q_hwp = check_input("q_hwp", q_hwp, shape=q_bore.shape)
|
|
1785
|
+
|
|
1786
|
+
if return_pa:
|
|
1787
|
+
qp.qp_bore2pixpa_hwp(
|
|
1788
|
+
self._memory, q_off, ctime, q_bore, q_hwp, nside, pix, pa, n
|
|
1789
|
+
)
|
|
1790
|
+
else:
|
|
1791
|
+
qp.qp_bore2pix_hwp(
|
|
1792
|
+
self._memory,
|
|
1793
|
+
q_off,
|
|
1794
|
+
ctime,
|
|
1795
|
+
q_bore,
|
|
1796
|
+
q_hwp,
|
|
1797
|
+
nside,
|
|
1798
|
+
pix,
|
|
1799
|
+
sin2psi,
|
|
1800
|
+
cos2psi,
|
|
1801
|
+
n,
|
|
1802
|
+
)
|
|
1803
|
+
|
|
1804
|
+
if pol is True:
|
|
1805
|
+
if return_pa:
|
|
1806
|
+
return pix, pa
|
|
1807
|
+
return pix, sin2psi, cos2psi
|
|
1808
|
+
return pix
|
|
1809
|
+
|
|
1810
|
+
def get_interp_val(self, map_in, ra, dec, nest=False):
|
|
1811
|
+
"""
|
|
1812
|
+
Interpolate map pixels to these coordinates. Uses a C implementation
|
|
1813
|
+
of the bilinear interpolation method `get_interpol()` as implemented
|
|
1814
|
+
in the equivalent healpix_cxx / healpy function.
|
|
1815
|
+
|
|
1816
|
+
Arguments
|
|
1817
|
+
---------
|
|
1818
|
+
map_in : array_like
|
|
1819
|
+
A single healpix map or list of maps which to interpolate from.
|
|
1820
|
+
ra, dec: array_like
|
|
1821
|
+
Timestreams of coordinates to interpolate to, in degrees,
|
|
1822
|
+
broadcastable to a common shape (nsample,)
|
|
1823
|
+
nest: bool, optional
|
|
1824
|
+
If True, input map is in the nested pixel ordering scheme.
|
|
1825
|
+
Otherwise, ring ordering is assumed.
|
|
1826
|
+
Default: False.
|
|
1827
|
+
|
|
1828
|
+
Returns
|
|
1829
|
+
-------
|
|
1830
|
+
values : array_like
|
|
1831
|
+
Array of interpolated map values, of shape (nmap, nsample).
|
|
1832
|
+
"""
|
|
1833
|
+
|
|
1834
|
+
pix_order = self.get("pix_order")
|
|
1835
|
+
if nest:
|
|
1836
|
+
self.set(pix_order="nest")
|
|
1837
|
+
else:
|
|
1838
|
+
self.set(pix_order="ring")
|
|
1839
|
+
|
|
1840
|
+
ra, dec = check_inputs(ra, dec)
|
|
1841
|
+
n = ra.size
|
|
1842
|
+
|
|
1843
|
+
from .qmap_class import check_map
|
|
1844
|
+
|
|
1845
|
+
map_in, nside = check_map(map_in)
|
|
1846
|
+
|
|
1847
|
+
val = check_output("value", shape=(len(map_in), n))
|
|
1848
|
+
|
|
1849
|
+
for m, v in zip(map_in, val):
|
|
1850
|
+
qp.qp_get_interp_valn(self._memory, nside, m, ra, dec, v, n)
|
|
1851
|
+
|
|
1852
|
+
self.set(pix_order=pix_order)
|
|
1853
|
+
|
|
1854
|
+
v = v.squeeze()
|
|
1855
|
+
if not v.shape:
|
|
1856
|
+
return v[()]
|
|
1857
|
+
return v
|
|
1858
|
+
|
|
1859
|
+
def update_bulletin_a(self, start_year=2000):
|
|
1860
|
+
"""
|
|
1861
|
+
Update the IERS Bulletin A database using astropy, and return the stored
|
|
1862
|
+
entries. Issues an ImportWarning if astropy version 1.2 or newer is not
|
|
1863
|
+
found.
|
|
1864
|
+
|
|
1865
|
+
Arguments
|
|
1866
|
+
---------
|
|
1867
|
+
start_year : int, optional
|
|
1868
|
+
Oldest year for which data should be stored.
|
|
1869
|
+
|
|
1870
|
+
Returns
|
|
1871
|
+
-------
|
|
1872
|
+
mjd : array_like
|
|
1873
|
+
Modified Julian date
|
|
1874
|
+
dut1 : array_like
|
|
1875
|
+
UT1-UTC time correction
|
|
1876
|
+
x : array_like
|
|
1877
|
+
y : array_like
|
|
1878
|
+
Polar motion (wobble) corrections
|
|
1879
|
+
"""
|
|
1880
|
+
try:
|
|
1881
|
+
from astropy.utils.iers import IERS_Auto
|
|
1882
|
+
except ImportError:
|
|
1883
|
+
warn(
|
|
1884
|
+
"Compatible Astropy not found. Install astropy v1.2 or newer "
|
|
1885
|
+
"for accurate polar motion and UT1 corrections",
|
|
1886
|
+
ImportWarning,
|
|
1887
|
+
)
|
|
1888
|
+
return
|
|
1889
|
+
|
|
1890
|
+
columns = ["MJD", "UT1_UTC", "PM_x", "PM_y", "year"]
|
|
1891
|
+
iers_table = IERS_Auto.open()[columns].as_array()
|
|
1892
|
+
|
|
1893
|
+
# check year
|
|
1894
|
+
year = iers_table["year"] + 1900
|
|
1895
|
+
(wraps,) = np.where(np.ediff1d(year) < 0)
|
|
1896
|
+
for idx in wraps:
|
|
1897
|
+
year[idx + 1 :] += 100
|
|
1898
|
+
iers_table["year"] = year
|
|
1899
|
+
iers_table = iers_table[year >= start_year]
|
|
1900
|
+
|
|
1901
|
+
# check MJD
|
|
1902
|
+
mjds = iers_table["MJD"]
|
|
1903
|
+
mjd_min = int(mjds.min())
|
|
1904
|
+
mjd_max = int(mjds.max())
|
|
1905
|
+
|
|
1906
|
+
# update table
|
|
1907
|
+
dut1 = np.array(iers_table["UT1_UTC"])
|
|
1908
|
+
x = np.array(iers_table["PM_x"])
|
|
1909
|
+
y = np.array(iers_table["PM_y"])
|
|
1910
|
+
qp.qp_set_iers_bulletin_a(self._memory, mjd_min, mjd_max, dut1, x, y)
|
|
1911
|
+
|
|
1912
|
+
return mjds, dut1, x, y
|
|
1913
|
+
|
|
1914
|
+
def load_bulletin_a(self, filename, columns=["mjd", "dut1", "x", "y"], **kwargs):
|
|
1915
|
+
"""
|
|
1916
|
+
Load IERS Bulletin A from file and store in memory. The file must be
|
|
1917
|
+
readable using `numpy.loadtxt` with `unpack=True`, and is assumed to be
|
|
1918
|
+
sorted by mjd.
|
|
1919
|
+
|
|
1920
|
+
Arguments
|
|
1921
|
+
---------
|
|
1922
|
+
filename : string
|
|
1923
|
+
Name of the text file containing IERS Bulletin A parameters.
|
|
1924
|
+
columns : list of strings
|
|
1925
|
+
list of columns as they appear in the file.
|
|
1926
|
+
A KeyError is raise if the list does not contain
|
|
1927
|
+
each of ['mjd', 'dut1', 'x', 'y'].
|
|
1928
|
+
|
|
1929
|
+
Any other keyword arguments are passed to the `numpy.loadtxt` function
|
|
1930
|
+
|
|
1931
|
+
Returns
|
|
1932
|
+
-------
|
|
1933
|
+
mjd : array_like
|
|
1934
|
+
Modified Julian date
|
|
1935
|
+
dut1 : array_like
|
|
1936
|
+
UT1-UTC time correction
|
|
1937
|
+
x : array_like
|
|
1938
|
+
y : array_like
|
|
1939
|
+
Polar motion corrections
|
|
1940
|
+
"""
|
|
1941
|
+
|
|
1942
|
+
req_columns = ["mjd", "dut1", "x", "y"]
|
|
1943
|
+
if not set(req_columns) <= set(columns):
|
|
1944
|
+
raise KeyError(
|
|
1945
|
+
"Missing columns {}".format(list(set(req_columns) - set(columns)))
|
|
1946
|
+
)
|
|
1947
|
+
kwargs["unpack"] = True
|
|
1948
|
+
data = np.loadtxt(filename, **kwargs)
|
|
1949
|
+
mjd, x, y, dut1 = (data[columns.index(x)] for x in req_columns)
|
|
1950
|
+
mjd_min, mjd_max = int(mjd[0]), int(mjd[-1])
|
|
1951
|
+
|
|
1952
|
+
try:
|
|
1953
|
+
qp.qp_set_iers_bulletin_a(self._memory, mjd_min, mjd_max, dut1, x, y)
|
|
1954
|
+
except:
|
|
1955
|
+
raise RuntimeError(
|
|
1956
|
+
"Error loading Bulletin A data from file {}".format(filename)
|
|
1957
|
+
)
|
|
1958
|
+
|
|
1959
|
+
return mjd, dut1, x, y
|
|
1960
|
+
|
|
1961
|
+
def get_bulletin_a(self, mjd):
|
|
1962
|
+
"""
|
|
1963
|
+
Return dut1/x/y for given mjd. Numpy-vectorized.
|
|
1964
|
+
"""
|
|
1965
|
+
|
|
1966
|
+
def func(x):
|
|
1967
|
+
return lib.qp_get_bulletin_a(self._memory, x)
|
|
1968
|
+
|
|
1969
|
+
fvec = np.vectorize(func, [np.double] * 3)
|
|
1970
|
+
|
|
1971
|
+
out = fvec(mjd)
|
|
1972
|
+
if out[0].shape == ():
|
|
1973
|
+
return tuple(x[()] for x in out)
|
|
1974
|
+
return out
|