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/qmap_class.py
ADDED
|
@@ -0,0 +1,1378 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from .qpoint_class import QPoint
|
|
3
|
+
import ctypes as ct
|
|
4
|
+
|
|
5
|
+
from . import _libqpoint as lib
|
|
6
|
+
from ._libqpoint import libqp as qp
|
|
7
|
+
|
|
8
|
+
__all__ = ["QMap", "check_map", "check_proj"]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# healpix bookkeeping
|
|
12
|
+
def nside2npix(nside):
|
|
13
|
+
"""
|
|
14
|
+
Convert healpix resolution (nside) to the number of healpix pixels in a full-sky map.
|
|
15
|
+
"""
|
|
16
|
+
return 12 * nside * nside
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def npix2nside(npix):
|
|
20
|
+
"""
|
|
21
|
+
Convert the number of healpix pixels in a full-sky map to healpix resolution (nside).
|
|
22
|
+
"""
|
|
23
|
+
nside = np.sqrt(npix / 12.0)
|
|
24
|
+
if nside != np.floor(nside):
|
|
25
|
+
raise ValueError(
|
|
26
|
+
"Invalid number of healpix pixels, must be npix = 12 * nside**2"
|
|
27
|
+
)
|
|
28
|
+
return int(nside)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def check_map(map_in, copy=False, partial=False, dtype=np.double):
|
|
32
|
+
"""
|
|
33
|
+
Return a properly transposed and memory-aligned map and its nside.
|
|
34
|
+
|
|
35
|
+
Arguments
|
|
36
|
+
---------
|
|
37
|
+
map_in : map or list of maps
|
|
38
|
+
Input map(s)
|
|
39
|
+
copy : bool, optional
|
|
40
|
+
If True, ensure that output map does not share memory with
|
|
41
|
+
the input map. Use if you do not want in-place operations to
|
|
42
|
+
modify the map contents.
|
|
43
|
+
partial : bool, optional
|
|
44
|
+
If True, the map is not checked to ensure a proper healpix nside,
|
|
45
|
+
and the number of pixels is returned instead.
|
|
46
|
+
|
|
47
|
+
Returns
|
|
48
|
+
-------
|
|
49
|
+
map_out : numpy.ndarray
|
|
50
|
+
Properly shaped and memory-aligned map, copied from the input
|
|
51
|
+
if necessary.
|
|
52
|
+
nside or npix : int
|
|
53
|
+
If partial is False, the map nside. Otherwise, the number of pixels.
|
|
54
|
+
"""
|
|
55
|
+
map_out = np.atleast_2d(map_in)
|
|
56
|
+
if np.argmax(map_out.shape) == 0:
|
|
57
|
+
map_out = map_out.T
|
|
58
|
+
if partial:
|
|
59
|
+
dim2 = len(map_out[0])
|
|
60
|
+
else:
|
|
61
|
+
dim2 = npix2nside(len(map_out[0]))
|
|
62
|
+
map_out = lib.check_input("map", map_out, dtype=dtype)
|
|
63
|
+
if copy and np.may_share_memory(map_in, map_out):
|
|
64
|
+
map_out = map_out.copy()
|
|
65
|
+
return map_out, dim2
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def check_proj(proj_in, copy=False, partial=False):
|
|
69
|
+
"""
|
|
70
|
+
Return a properly transposed and memory-aligned projection map,
|
|
71
|
+
its nside, and the map dimension.
|
|
72
|
+
|
|
73
|
+
Arguments
|
|
74
|
+
---------
|
|
75
|
+
proj_in : map or list of maps
|
|
76
|
+
Input projection matrix map
|
|
77
|
+
copy : bool, optional
|
|
78
|
+
If True, ensure that output map does not share memory with
|
|
79
|
+
the input map. Use if you do not want in-place operations to
|
|
80
|
+
modify the map contents.
|
|
81
|
+
partial : bool, optional
|
|
82
|
+
If True, the map is not checked to ensure a proper healpix nside,
|
|
83
|
+
and the number of pixels is returned instead.
|
|
84
|
+
|
|
85
|
+
Returns
|
|
86
|
+
-------
|
|
87
|
+
map_out : numpy.ndarray
|
|
88
|
+
Properly shaped and memory-aligned map, copied from the input
|
|
89
|
+
if necessary.
|
|
90
|
+
nside or npix : int
|
|
91
|
+
If partial is False, the map nside. Otherwise, the number of pixels.
|
|
92
|
+
nmap : int
|
|
93
|
+
The map size this projection matrix is intended to invert, i.e.
|
|
94
|
+
the solution to `len(proj) = nmap * (nmap + 1) / 2`. Raises an
|
|
95
|
+
error if an integer solution is not found.
|
|
96
|
+
"""
|
|
97
|
+
proj_out, dim2 = check_map(proj_in, copy=copy, partial=partial)
|
|
98
|
+
nmap = int((np.sqrt(8 * len(proj_out) + 1) - 1)) // 2
|
|
99
|
+
if nmap * (nmap + 1) // 2 != len(proj_out):
|
|
100
|
+
raise ValueError("proj has incompatible shape")
|
|
101
|
+
return proj_out, dim2, nmap
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class QMap(QPoint):
|
|
105
|
+
"""
|
|
106
|
+
Quaternion-based mapmaker that generates per-channel pointing
|
|
107
|
+
on-the-fly.
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
def __init__(
|
|
111
|
+
self,
|
|
112
|
+
nside=None,
|
|
113
|
+
pol=True,
|
|
114
|
+
vpol=False,
|
|
115
|
+
source_map=None,
|
|
116
|
+
source_pol=True,
|
|
117
|
+
source_vpol=False,
|
|
118
|
+
q_bore=None,
|
|
119
|
+
ctime=None,
|
|
120
|
+
q_hwp=None,
|
|
121
|
+
**kwargs,
|
|
122
|
+
):
|
|
123
|
+
"""
|
|
124
|
+
Initialize the internal structures and data depo for
|
|
125
|
+
mapmaking and/or timestream generation.
|
|
126
|
+
|
|
127
|
+
Arguments
|
|
128
|
+
---------
|
|
129
|
+
nside : int, optional
|
|
130
|
+
Resolution of the output maps. Default: None
|
|
131
|
+
If None, dest is not initialized.
|
|
132
|
+
pol : bool, optional
|
|
133
|
+
If True, output maps are polarized.
|
|
134
|
+
vpol : bool, optional
|
|
135
|
+
If True, output maps contain V polarization.
|
|
136
|
+
source_map : array_like, optional
|
|
137
|
+
If supplied, passed to :meth:`init_source` to initialize the source
|
|
138
|
+
map structure.
|
|
139
|
+
source_pol : bool, optional
|
|
140
|
+
If True, source_map is polarized. See :meth:`init_source` for
|
|
141
|
+
details.
|
|
142
|
+
source_vpol : bool, optional
|
|
143
|
+
If True, source_map contains V polarization.
|
|
144
|
+
q_bore, ctime, q_hwp : array_like, optional
|
|
145
|
+
Boresight pointing data. See :meth:`init_point` for details. If
|
|
146
|
+
not supplied, the pointing structure is left uninitialized.
|
|
147
|
+
|
|
148
|
+
Notes
|
|
149
|
+
-----
|
|
150
|
+
Remaining keyword arguments are passed to the
|
|
151
|
+
:meth:`~qpoint.qpoint_class.QPoint.set` method.
|
|
152
|
+
|
|
153
|
+
An internal :attr:`depo` dictionary attribute stores the source and output
|
|
154
|
+
maps, timestreams, and pointing data for retrieval by the user.
|
|
155
|
+
Only pointers to these arrays in memory are passed to the C
|
|
156
|
+
library. To ensure that extraneous copies of data are not made,
|
|
157
|
+
supply these methods with C-contiguous arrays of the correct shape.
|
|
158
|
+
"""
|
|
159
|
+
super(QMap, self).__init__(**kwargs)
|
|
160
|
+
|
|
161
|
+
self.depo = dict()
|
|
162
|
+
"""
|
|
163
|
+
Dictionary of source and output maps, timetreams and pointing data.
|
|
164
|
+
Pointers to these arrays in memory are passed to the C library.
|
|
165
|
+
"""
|
|
166
|
+
|
|
167
|
+
self.reset()
|
|
168
|
+
|
|
169
|
+
if nside is not None:
|
|
170
|
+
self.init_dest(nside=nside, pol=pol, vpol=vpol)
|
|
171
|
+
|
|
172
|
+
if source_map is not None:
|
|
173
|
+
self.init_source(source_map, pol=source_pol, vpol=source_vpol)
|
|
174
|
+
|
|
175
|
+
if q_bore is not None:
|
|
176
|
+
self.init_point(q_bore, ctime=ctime, q_hwp=q_hwp)
|
|
177
|
+
|
|
178
|
+
def source_is_init(self):
|
|
179
|
+
"""
|
|
180
|
+
Return True if the source map is initialized, otherwise False.
|
|
181
|
+
"""
|
|
182
|
+
if not hasattr(self, "_source"):
|
|
183
|
+
return False
|
|
184
|
+
if not self._source.contents.init:
|
|
185
|
+
return False
|
|
186
|
+
return True
|
|
187
|
+
|
|
188
|
+
def init_source(
|
|
189
|
+
self,
|
|
190
|
+
source_map,
|
|
191
|
+
pol=True,
|
|
192
|
+
pixels=None,
|
|
193
|
+
nside=None,
|
|
194
|
+
vpol=False,
|
|
195
|
+
reset=False,
|
|
196
|
+
update=False,
|
|
197
|
+
):
|
|
198
|
+
"""
|
|
199
|
+
Initialize the source map structure. Timestreams are
|
|
200
|
+
produced by scanning this map.
|
|
201
|
+
|
|
202
|
+
Arguments
|
|
203
|
+
---------
|
|
204
|
+
source_map : array_like
|
|
205
|
+
Input map. Must be of shape `(N, npix)`, where `N` can be
|
|
206
|
+
1, 3, 6, 9, or 18.
|
|
207
|
+
pol : bool, optional
|
|
208
|
+
If `True`, and the map shape is `(3, npix)`, then input is a
|
|
209
|
+
polarized map (and not T + first derivatives).
|
|
210
|
+
pixels : 1D array_like, optional
|
|
211
|
+
Array of pixel numbers for each map index, if `source_map` is
|
|
212
|
+
a partial map.
|
|
213
|
+
nside : int, optional
|
|
214
|
+
map dimension. If `pixels` is supplied, this argument is required.
|
|
215
|
+
Otherwise, the nside is determined from the input map.
|
|
216
|
+
vpol : bool, optional
|
|
217
|
+
If `True`, and the input map shape is `(4, npix)`, then input is
|
|
218
|
+
a polarized map that includes V polarization.
|
|
219
|
+
reset : bool, optional
|
|
220
|
+
If `True`, and if the structure has already been initialized,
|
|
221
|
+
it is reset and re-initialized with the new map. If `False`,
|
|
222
|
+
a `RuntimeError` is raised if the structure has already been
|
|
223
|
+
initialized.
|
|
224
|
+
update : bool, optional
|
|
225
|
+
If `True`, and if the structure has already been initialized,
|
|
226
|
+
the supplied `source_map` is replaced in the existing source
|
|
227
|
+
structure rather than reinitializing from scratch.
|
|
228
|
+
|
|
229
|
+
Notes
|
|
230
|
+
-----
|
|
231
|
+
This method will automatically determine the type of map
|
|
232
|
+
given its shape. Note that for `N=3`, the `pol` keyword argument
|
|
233
|
+
should be used to disambiguate the two map types. By default,
|
|
234
|
+
a polarized map with `(T,Q,U)` components is assumed.
|
|
235
|
+
|
|
236
|
+
* A map of shape `(1, npix)` or `(npix,)` contains only a `T` map.
|
|
237
|
+
* A map of shape `(3, npix)` contains `(T, Q, U)` if `pol` is `True`,
|
|
238
|
+
or `(T, dTdt, dTdp)` if `pol` is `False`.
|
|
239
|
+
* A map of shape `(4, npix)` contains `(T, Q, U, V)`.
|
|
240
|
+
* A map of shape `(6, npix)` contains `(T, dTdt, dTdp, dT2dt2,
|
|
241
|
+
dT2dpdt, dT2dp2)`.
|
|
242
|
+
* A map of shape `(9, npix)` contains `(T, Q, U, dTdt, dQdt, dUdt,
|
|
243
|
+
dTdp, dQdp, dUdp)`.
|
|
244
|
+
* A map of shape `(18, npix)` contains all the columns of the
|
|
245
|
+
9-column map, followed by `(dT2dt2, dQ2dt2, dU2dt2, dT2dpdt,
|
|
246
|
+
dQ2dpdt, dU2dpdt, dT2dp2, dQ2dp2, dU2dp2)`.
|
|
247
|
+
"""
|
|
248
|
+
|
|
249
|
+
if self.source_is_init():
|
|
250
|
+
if reset:
|
|
251
|
+
self.reset_source()
|
|
252
|
+
|
|
253
|
+
elif update:
|
|
254
|
+
source = self._source.contents
|
|
255
|
+
if (
|
|
256
|
+
source_map.squeeze().shape[-1]
|
|
257
|
+
!= self.depo["source_map"].squeeze().shape[-1]
|
|
258
|
+
):
|
|
259
|
+
raise ValueError("source_map shape mismatch")
|
|
260
|
+
source_map, _ = check_map(source_map, partial=True)
|
|
261
|
+
source.num_vec = len(source_map)
|
|
262
|
+
source.vec_mode = lib.get_vec_mode(source_map, pol, vpol)
|
|
263
|
+
source.vec1d = lib.as_ctypes(source_map.ravel())
|
|
264
|
+
self.depo["source_map"] = source_map
|
|
265
|
+
if qp.qp_reshape_map(self._source):
|
|
266
|
+
raise RuntimeError("Error reshaping source map")
|
|
267
|
+
return
|
|
268
|
+
|
|
269
|
+
else:
|
|
270
|
+
raise RuntimeError("source already initialized")
|
|
271
|
+
|
|
272
|
+
if pixels is None:
|
|
273
|
+
partial = False
|
|
274
|
+
else:
|
|
275
|
+
partial = True
|
|
276
|
+
if nside is None:
|
|
277
|
+
raise ValueError("nside required for partial maps")
|
|
278
|
+
|
|
279
|
+
# check map shape and create pointer
|
|
280
|
+
smap, snside = check_map(source_map, partial=partial)
|
|
281
|
+
if not partial:
|
|
282
|
+
nside = snside
|
|
283
|
+
npix = nside2npix(nside)
|
|
284
|
+
else:
|
|
285
|
+
npix = len(pixels)
|
|
286
|
+
|
|
287
|
+
# store map
|
|
288
|
+
self.depo["source_map"] = smap
|
|
289
|
+
self.depo["source_nside"] = nside
|
|
290
|
+
if partial:
|
|
291
|
+
self.depo["source_pixels"] = pixels
|
|
292
|
+
|
|
293
|
+
# initialize
|
|
294
|
+
source = self._source.contents
|
|
295
|
+
source.partial = partial
|
|
296
|
+
source.nside = nside
|
|
297
|
+
source.npix = npix
|
|
298
|
+
source.pixinfo_init = 0
|
|
299
|
+
source.pixinfo = None
|
|
300
|
+
source.pixhash_init = 0
|
|
301
|
+
source.pixhash = None
|
|
302
|
+
source.num_vec = len(source_map)
|
|
303
|
+
source.vec_mode = lib.get_vec_mode(smap, pol, vpol)
|
|
304
|
+
source.vec1d = lib.as_ctypes(smap.ravel())
|
|
305
|
+
source.vec1d_init = lib.QP_ARR_INIT_PTR
|
|
306
|
+
source.vec = None
|
|
307
|
+
source.vec_init = 0
|
|
308
|
+
source.num_proj = 0
|
|
309
|
+
source.proj_mode = 0
|
|
310
|
+
source.proj = None
|
|
311
|
+
source.proj_init = 0
|
|
312
|
+
source.proj1d = None
|
|
313
|
+
source.proj1d_init = 0
|
|
314
|
+
source.init = lib.QP_STRUCT_INIT
|
|
315
|
+
|
|
316
|
+
if partial:
|
|
317
|
+
if qp.qp_init_map_pixhash(self._source, pixels, npix):
|
|
318
|
+
raise RuntimeError("Error initializing source pixhash")
|
|
319
|
+
|
|
320
|
+
if qp.qp_reshape_map(self._source):
|
|
321
|
+
raise RuntimeError("Error reshaping source map")
|
|
322
|
+
|
|
323
|
+
def reset_source(self):
|
|
324
|
+
"""
|
|
325
|
+
Reset the source map structure. Must be reinitialized to
|
|
326
|
+
produce more timestreams.
|
|
327
|
+
"""
|
|
328
|
+
if hasattr(self, "_source"):
|
|
329
|
+
qp.qp_free_map(self._source)
|
|
330
|
+
self.depo.pop("source_map", None)
|
|
331
|
+
self.depo.pop("source_nside", None)
|
|
332
|
+
self.depo.pop("source_pixels", None)
|
|
333
|
+
self._source = ct.pointer(lib.qp_map_t())
|
|
334
|
+
|
|
335
|
+
def source_is_pol(self):
|
|
336
|
+
"""
|
|
337
|
+
Return True if the source map is polarized, otherwise False.
|
|
338
|
+
Raise an error if source map is not initialized.
|
|
339
|
+
"""
|
|
340
|
+
if not self.source_is_init():
|
|
341
|
+
raise RuntimeError("source map not initialized")
|
|
342
|
+
|
|
343
|
+
if self._source.contents.vec_mode in [2, 3, 5, 7]:
|
|
344
|
+
return True
|
|
345
|
+
return False
|
|
346
|
+
|
|
347
|
+
def source_is_vpol(self):
|
|
348
|
+
"""
|
|
349
|
+
Return True if the source map contains V polarization,
|
|
350
|
+
otherwise False.
|
|
351
|
+
Raise an error if source map is not initialized.
|
|
352
|
+
"""
|
|
353
|
+
if not self.source_is_init():
|
|
354
|
+
raise RuntimeError("source map not initialized")
|
|
355
|
+
|
|
356
|
+
return self._source.contents.vec_mode == 3
|
|
357
|
+
|
|
358
|
+
def dest_is_init(self):
|
|
359
|
+
"""
|
|
360
|
+
Return True if the dest map is initialized, otherwise False.
|
|
361
|
+
"""
|
|
362
|
+
if not hasattr(self, "_dest"):
|
|
363
|
+
return False
|
|
364
|
+
if not self._dest.contents.init:
|
|
365
|
+
return False
|
|
366
|
+
return True
|
|
367
|
+
|
|
368
|
+
def init_dest(
|
|
369
|
+
self,
|
|
370
|
+
nside=None,
|
|
371
|
+
pol=True,
|
|
372
|
+
vec=None,
|
|
373
|
+
proj=None,
|
|
374
|
+
pixels=None,
|
|
375
|
+
vpol=False,
|
|
376
|
+
copy=False,
|
|
377
|
+
reset=False,
|
|
378
|
+
update=False,
|
|
379
|
+
):
|
|
380
|
+
"""
|
|
381
|
+
Initialize the destination map structure. Timestreams are binned
|
|
382
|
+
and projection matrices accumulated into this structure.
|
|
383
|
+
|
|
384
|
+
Arguments
|
|
385
|
+
---------
|
|
386
|
+
nside : int, optional
|
|
387
|
+
map dimension. If `pixels` is supplied, this argument is required.
|
|
388
|
+
Otherwise, the default is 256.
|
|
389
|
+
pol : bool, optional
|
|
390
|
+
If True, a polarized map will be created.
|
|
391
|
+
vec : array_like or bool, optional, shape (N, npix)
|
|
392
|
+
If supplied, nside and pol are determined from this map, and
|
|
393
|
+
the vector (binned signal) map is initialized from this.
|
|
394
|
+
If False, accumulation of this map from timestreams is disabled.
|
|
395
|
+
proj : array_like or bool, optional, shape (N*(N+1)/2, npix)
|
|
396
|
+
Array of upper-triangular elements of the projection matrix
|
|
397
|
+
for each pixel. If not supplied, a blank map of the appropriate
|
|
398
|
+
shape is created. If False, accumulation of the projection matrix
|
|
399
|
+
is disabled.
|
|
400
|
+
pixels : 1D array_like, optional
|
|
401
|
+
Array of pixel numbers for each map index, if `vec` and `proj` are
|
|
402
|
+
partial maps.
|
|
403
|
+
vpol : bool, optional
|
|
404
|
+
If True, a polarized map including V polarization will be created.
|
|
405
|
+
copy : bool, optional
|
|
406
|
+
If True and vec/proj are supplied, make copies of these inputs
|
|
407
|
+
to avoid in-place operations.
|
|
408
|
+
reset : bool, optional
|
|
409
|
+
If True, and if the structure has already been initialized,
|
|
410
|
+
it is reset and re-initialized with the new map. If False,
|
|
411
|
+
a RuntimeError is raised if the structure has already been
|
|
412
|
+
initialized.
|
|
413
|
+
update : bool, optional
|
|
414
|
+
If True, and if the structure has already been initialized,
|
|
415
|
+
the supplied vec and proj are replaced in the existing dest
|
|
416
|
+
structure rather than reinitializing from scratch.
|
|
417
|
+
"""
|
|
418
|
+
|
|
419
|
+
if vec is False and proj is False:
|
|
420
|
+
raise ValueError("one of vec or proj must not be False")
|
|
421
|
+
|
|
422
|
+
if self.dest_is_init():
|
|
423
|
+
if reset:
|
|
424
|
+
self.reset_dest()
|
|
425
|
+
|
|
426
|
+
elif update:
|
|
427
|
+
# update map data with same shape
|
|
428
|
+
|
|
429
|
+
dest = self._dest.contents
|
|
430
|
+
ret = ()
|
|
431
|
+
|
|
432
|
+
if self.depo["vec"] is not False:
|
|
433
|
+
if vec is None:
|
|
434
|
+
vec = np.zeros_like(self.depo["vec"])
|
|
435
|
+
if vec.squeeze().shape[-1] != self.depo["vec"].squeeze().shape[-1]:
|
|
436
|
+
raise ValueError("vec shape mismatch")
|
|
437
|
+
vec, _ = check_map(vec, copy=copy, partial=True)
|
|
438
|
+
dest.num_vec = len(vec)
|
|
439
|
+
dest.vec_mode = lib.get_vec_mode(vec, pol, vpol)
|
|
440
|
+
dest.vec1d = lib.as_ctypes(vec.ravel())
|
|
441
|
+
self.depo["vec"] = vec
|
|
442
|
+
ret += (vec.squeeze(),)
|
|
443
|
+
|
|
444
|
+
if self.depo["proj"] is not False:
|
|
445
|
+
if proj is None:
|
|
446
|
+
proj = np.zeros_like(self.depo["proj"])
|
|
447
|
+
if (
|
|
448
|
+
proj.squeeze().shape[-1]
|
|
449
|
+
!= self.depo["proj"].squeeze().shape[-1]
|
|
450
|
+
):
|
|
451
|
+
raise ValueError("proj shape mismatch")
|
|
452
|
+
proj, _ = check_map(proj, copy=copy, partial=True)
|
|
453
|
+
dest.num_proj = len(proj)
|
|
454
|
+
dest.proj_mode = lib.get_proj_mode(proj, pol, vpol)
|
|
455
|
+
dest.proj1d = lib.as_ctypes(proj.ravel())
|
|
456
|
+
self.depo["proj"] = proj
|
|
457
|
+
ret += (proj.squeeze(),)
|
|
458
|
+
|
|
459
|
+
if qp.qp_reshape_map(self._dest):
|
|
460
|
+
raise RuntimeError("Error reshaping dest map")
|
|
461
|
+
|
|
462
|
+
if len(ret) == 1:
|
|
463
|
+
return ret[0]
|
|
464
|
+
return ret
|
|
465
|
+
|
|
466
|
+
else:
|
|
467
|
+
raise RuntimeError("dest already initialized")
|
|
468
|
+
|
|
469
|
+
if pixels is None:
|
|
470
|
+
if nside is None:
|
|
471
|
+
nside = 256
|
|
472
|
+
npix = nside2npix(nside)
|
|
473
|
+
partial = False
|
|
474
|
+
else:
|
|
475
|
+
if nside is None:
|
|
476
|
+
raise ValueError("nside required for partial maps")
|
|
477
|
+
npix = len(pixels)
|
|
478
|
+
partial = True
|
|
479
|
+
|
|
480
|
+
if vec is None:
|
|
481
|
+
if vpol:
|
|
482
|
+
vec = np.zeros((4, npix), dtype=np.double)
|
|
483
|
+
elif pol:
|
|
484
|
+
vec = np.zeros((3, npix), dtype=np.double)
|
|
485
|
+
else:
|
|
486
|
+
vec = np.zeros((1, npix), dtype=np.double)
|
|
487
|
+
vdim2 = npix if partial else nside
|
|
488
|
+
elif vec is not False:
|
|
489
|
+
vec, vdim2 = check_map(vec, copy=copy, partial=partial)
|
|
490
|
+
if not partial:
|
|
491
|
+
nside = vdim2
|
|
492
|
+
npix = nside2npix(nside)
|
|
493
|
+
elif vdim2 != npix:
|
|
494
|
+
raise ValueError("vec has incompatible shape")
|
|
495
|
+
if len(vec) == 1:
|
|
496
|
+
pol = False
|
|
497
|
+
vpol = False
|
|
498
|
+
elif len(vec) == 3:
|
|
499
|
+
pol = True
|
|
500
|
+
vpol = False
|
|
501
|
+
elif len(vec) == 4:
|
|
502
|
+
pol = True
|
|
503
|
+
vpol = True
|
|
504
|
+
else:
|
|
505
|
+
raise ValueError("vec has incompatible shape")
|
|
506
|
+
|
|
507
|
+
if proj is None:
|
|
508
|
+
if vpol:
|
|
509
|
+
proj = np.zeros((10, npix), dtype=np.double)
|
|
510
|
+
elif pol:
|
|
511
|
+
proj = np.zeros((6, npix), dtype=np.double)
|
|
512
|
+
else:
|
|
513
|
+
proj = np.zeros((1, npix), dtype=np.double)
|
|
514
|
+
pdim2 = npix if partial else nside
|
|
515
|
+
elif proj is not False:
|
|
516
|
+
proj, pdim2, pnmap = check_proj(proj, copy=copy, partial=partial)
|
|
517
|
+
|
|
518
|
+
if vec is not False:
|
|
519
|
+
if pnmap != len(vec):
|
|
520
|
+
raise ValueError("proj has incompatible shape")
|
|
521
|
+
if len(proj) != [[1, 6][pol], 10][vpol]:
|
|
522
|
+
raise ValueError("proj has incompatible shape")
|
|
523
|
+
if pdim2 != vdim2:
|
|
524
|
+
raise ValueError("proj has incompatible nside")
|
|
525
|
+
else:
|
|
526
|
+
if len(proj) == 1:
|
|
527
|
+
pol = False
|
|
528
|
+
vpol = False
|
|
529
|
+
elif len(proj) == 6:
|
|
530
|
+
pol = True
|
|
531
|
+
vpol = False
|
|
532
|
+
elif len(proj) == 10:
|
|
533
|
+
pol = True
|
|
534
|
+
vpol = True
|
|
535
|
+
else:
|
|
536
|
+
raise ValueError("proj has incompatible shape")
|
|
537
|
+
if not partial:
|
|
538
|
+
nside = pdim2
|
|
539
|
+
npix = nside2npix(nside)
|
|
540
|
+
elif pdim2 != npix:
|
|
541
|
+
raise ValueError("proj has incompatible shape")
|
|
542
|
+
|
|
543
|
+
# store arrays for later retrieval
|
|
544
|
+
self.depo["vec"] = vec
|
|
545
|
+
self.depo["proj"] = proj
|
|
546
|
+
self.depo["dest_nside"] = nside
|
|
547
|
+
|
|
548
|
+
if partial:
|
|
549
|
+
self.depo["dest_pixels"] = pixels
|
|
550
|
+
|
|
551
|
+
# initialize
|
|
552
|
+
ret = ()
|
|
553
|
+
dest = self._dest.contents
|
|
554
|
+
dest.nside = nside
|
|
555
|
+
dest.npix = npix
|
|
556
|
+
dest.partial = partial
|
|
557
|
+
dest.pixinfo_init = 0
|
|
558
|
+
dest.pixinfo = None
|
|
559
|
+
dest.pixhash_init = 0
|
|
560
|
+
dest.pixhash = None
|
|
561
|
+
if vec is not False:
|
|
562
|
+
dest.num_vec = len(vec)
|
|
563
|
+
dest.vec_mode = lib.get_vec_mode(vec, pol, vpol)
|
|
564
|
+
dest.vec1d = lib.as_ctypes(vec.ravel())
|
|
565
|
+
dest.vec1d_init = lib.QP_ARR_INIT_PTR
|
|
566
|
+
ret += (vec.squeeze(),)
|
|
567
|
+
if proj is not False:
|
|
568
|
+
dest.num_proj = len(proj)
|
|
569
|
+
dest.proj_mode = lib.get_proj_mode(proj, pol, vpol)
|
|
570
|
+
dest.proj1d = lib.as_ctypes(proj.ravel())
|
|
571
|
+
dest.proj1d_init = lib.QP_ARR_INIT_PTR
|
|
572
|
+
ret += (proj.squeeze(),)
|
|
573
|
+
dest.vec = None
|
|
574
|
+
dest.vec_init = 0
|
|
575
|
+
dest.proj = None
|
|
576
|
+
dest.proj_init = 0
|
|
577
|
+
dest.init = lib.QP_STRUCT_INIT
|
|
578
|
+
|
|
579
|
+
if partial:
|
|
580
|
+
if qp.qp_init_map_pixhash(self._dest, pixels, npix):
|
|
581
|
+
raise RuntimeError("Error initializing dest pixhash")
|
|
582
|
+
|
|
583
|
+
if qp.qp_reshape_map(self._dest):
|
|
584
|
+
raise RuntimeError("Error reshaping dest map")
|
|
585
|
+
|
|
586
|
+
# return
|
|
587
|
+
if len(ret) == 1:
|
|
588
|
+
return ret[0]
|
|
589
|
+
return ret
|
|
590
|
+
|
|
591
|
+
def reset_dest(self):
|
|
592
|
+
"""
|
|
593
|
+
Reset the destination map structure.
|
|
594
|
+
Must be reinitialized to continue mapmaking.
|
|
595
|
+
"""
|
|
596
|
+
if hasattr(self, "_dest"):
|
|
597
|
+
qp.qp_free_map(self._dest)
|
|
598
|
+
self.depo.pop("vec", None)
|
|
599
|
+
self.depo.pop("proj", None)
|
|
600
|
+
self.depo.pop("dest_nside", None)
|
|
601
|
+
self.depo.pop("dest_pixels", None)
|
|
602
|
+
self._dest = ct.pointer(lib.qp_map_t())
|
|
603
|
+
|
|
604
|
+
def dest_is_pol(self):
|
|
605
|
+
"""
|
|
606
|
+
Return True if the destination map is polarized, otherwise False.
|
|
607
|
+
Raise an error if destination map is not initialized.
|
|
608
|
+
"""
|
|
609
|
+
if not self.dest_is_init():
|
|
610
|
+
raise RuntimeError("dest map not initialized")
|
|
611
|
+
|
|
612
|
+
if 2 in [self._dest.contents.vec_mode, self._dest.contents.proj_mode]:
|
|
613
|
+
return True
|
|
614
|
+
if 3 in [self._dest.contents.vec_mode, self._dest.contents.proj_mode]:
|
|
615
|
+
return True
|
|
616
|
+
return False
|
|
617
|
+
|
|
618
|
+
def dest_is_vpol(self):
|
|
619
|
+
"""
|
|
620
|
+
Return True if the destination map contains V polarization,
|
|
621
|
+
otherwise False.
|
|
622
|
+
Raise an error if destination map is not initialized.
|
|
623
|
+
"""
|
|
624
|
+
if not self.dest_is_init():
|
|
625
|
+
raise RuntimeError("dest map not initialized")
|
|
626
|
+
|
|
627
|
+
if 3 in [self._dest.contents.vec_mode, self._dest.contents.proj_mode]:
|
|
628
|
+
return True
|
|
629
|
+
return False
|
|
630
|
+
|
|
631
|
+
def point_is_init(self):
|
|
632
|
+
"""
|
|
633
|
+
Return True if the point map is initialized, otherwise False.
|
|
634
|
+
"""
|
|
635
|
+
if not hasattr(self, "_point"):
|
|
636
|
+
return False
|
|
637
|
+
if not self._point.contents.init:
|
|
638
|
+
return False
|
|
639
|
+
return True
|
|
640
|
+
|
|
641
|
+
def init_point(self, q_bore=None, ctime=None, q_hwp=None):
|
|
642
|
+
"""
|
|
643
|
+
Initialize or update the boresight pointing data structure.
|
|
644
|
+
|
|
645
|
+
Arguments
|
|
646
|
+
---------
|
|
647
|
+
q_bore : array_like, optional
|
|
648
|
+
Boresight pointing quaternion, of shape (nsamp, 4).
|
|
649
|
+
If supplied, the pointing structure is reset if already
|
|
650
|
+
initialized.
|
|
651
|
+
ctime : array_like, optional
|
|
652
|
+
time since the UTC epoch. If not None, the time array
|
|
653
|
+
is updated to this. Shape must be (nsamp,)
|
|
654
|
+
q_hwp : array_like, optional
|
|
655
|
+
Waveplate quaternion. If not None, the quaternion is
|
|
656
|
+
updated to this. Shape must be (nsamp, 4)
|
|
657
|
+
"""
|
|
658
|
+
|
|
659
|
+
if not hasattr(self, "_point"):
|
|
660
|
+
self.reset_point()
|
|
661
|
+
|
|
662
|
+
point = self._point.contents
|
|
663
|
+
|
|
664
|
+
if q_bore is not None:
|
|
665
|
+
if point.init:
|
|
666
|
+
self.reset_point()
|
|
667
|
+
point = self._point.contents
|
|
668
|
+
q_bore = lib.check_input("q_bore", np.atleast_2d(q_bore), quat=True)
|
|
669
|
+
n = q_bore.size // 4
|
|
670
|
+
point.n = n
|
|
671
|
+
self.depo["q_bore"] = q_bore
|
|
672
|
+
point.q_bore = lib.as_ctypes(q_bore)
|
|
673
|
+
point.q_bore_init = lib.QP_ARR_INIT_PTR
|
|
674
|
+
point.init = lib.QP_STRUCT_INIT
|
|
675
|
+
|
|
676
|
+
if not point.init:
|
|
677
|
+
raise RuntimeError("point not initialized")
|
|
678
|
+
|
|
679
|
+
n = point.n
|
|
680
|
+
|
|
681
|
+
if ctime is False:
|
|
682
|
+
point.ctime_init = 0
|
|
683
|
+
point.ctime = None
|
|
684
|
+
elif ctime is not None:
|
|
685
|
+
ctime = lib.check_input("ctime", ctime, shape=(n,))
|
|
686
|
+
self.depo["ctime"] = ctime
|
|
687
|
+
point.ctime_init = lib.QP_ARR_INIT_PTR
|
|
688
|
+
point.ctime = lib.as_ctypes(ctime)
|
|
689
|
+
|
|
690
|
+
if q_hwp is False:
|
|
691
|
+
point.q_hwp_init = 0
|
|
692
|
+
point.q_hwp = None
|
|
693
|
+
elif q_hwp is not None:
|
|
694
|
+
q_hwp = lib.check_input("q_hwp", q_hwp, shape=(n, 4), quat=True)
|
|
695
|
+
self.depo["q_hwp"] = q_hwp
|
|
696
|
+
point.q_hwp_init = lib.QP_ARR_INIT_PTR
|
|
697
|
+
point.q_hwp = lib.as_ctypes(q_hwp)
|
|
698
|
+
|
|
699
|
+
def reset_point(self):
|
|
700
|
+
"""
|
|
701
|
+
Reset the pointing data structure.
|
|
702
|
+
"""
|
|
703
|
+
if hasattr(self, "_point"):
|
|
704
|
+
qp.qp_free_point(self._point)
|
|
705
|
+
self.depo.pop("q_bore", None)
|
|
706
|
+
self.depo.pop("ctime", None)
|
|
707
|
+
self.depo.pop("q_hwp", None)
|
|
708
|
+
self._point = ct.pointer(lib.qp_point_t())
|
|
709
|
+
|
|
710
|
+
def init_detarr(
|
|
711
|
+
self,
|
|
712
|
+
q_off,
|
|
713
|
+
weight=None,
|
|
714
|
+
gain=None,
|
|
715
|
+
mueller=None,
|
|
716
|
+
tod=None,
|
|
717
|
+
flag=None,
|
|
718
|
+
weights=None,
|
|
719
|
+
do_diff=False,
|
|
720
|
+
write=False,
|
|
721
|
+
):
|
|
722
|
+
"""
|
|
723
|
+
Initialize the detector listing structure. Detector properties and
|
|
724
|
+
timestreams are passed to and from the mapmaker through this structure.
|
|
725
|
+
|
|
726
|
+
Arguments
|
|
727
|
+
---------
|
|
728
|
+
q_off : array_like
|
|
729
|
+
Array of offset quaternions, of shape (ndet, 4).
|
|
730
|
+
weight : array_like, optional
|
|
731
|
+
Per-channel mapmaking weights, of shape (ndet,) or a constant.
|
|
732
|
+
Default : 1.
|
|
733
|
+
gain : array_like, optional
|
|
734
|
+
Per-channel gains, of shape (ndet,) or a constant.
|
|
735
|
+
Default : 1.
|
|
736
|
+
mueller : array_like, optional
|
|
737
|
+
Per-channel polarization efficiencies, of shape(ndet, 4).
|
|
738
|
+
Default : [1., 1., 0., 1.] per det.
|
|
739
|
+
tod : array_like, optional
|
|
740
|
+
Timestream array, of shape (ndet, nsamp). nsamp must match that of
|
|
741
|
+
the pointing structure. If not supplied and `write` is True, then
|
|
742
|
+
a zero-filled timestream array is initialized.
|
|
743
|
+
flag : array_like, optional
|
|
744
|
+
Flag array, of shape (ndet, nsamp), for excluding data from
|
|
745
|
+
mapmaking. nsamp must match that of the pointing structure.
|
|
746
|
+
If not supplied, a zero-filled array is initialized (i.e. no
|
|
747
|
+
flagged samples).
|
|
748
|
+
weights : array_like, optional
|
|
749
|
+
Weight array, of shape (ndet, nsamp), for weighting each sample of
|
|
750
|
+
data. nsamp must match that of the pointing structure. If not
|
|
751
|
+
supplied, this option is not used.
|
|
752
|
+
do_diff : bool, optional
|
|
753
|
+
If True, initialize pairs of arrays for pair-differenced mapmaking.
|
|
754
|
+
write : bool, optional
|
|
755
|
+
If True, the timestreams are ensured writable and created if
|
|
756
|
+
necessary.
|
|
757
|
+
"""
|
|
758
|
+
|
|
759
|
+
self.reset_detarr()
|
|
760
|
+
|
|
761
|
+
# check inputs
|
|
762
|
+
q_off = lib.check_input("q_off", np.atleast_2d(q_off), quat=True)
|
|
763
|
+
n = q_off.size // 4
|
|
764
|
+
weight = lib.check_input("weight", weight, shape=(n,), fill=1)
|
|
765
|
+
gain = lib.check_input("gain", gain, shape=(n,), fill=1)
|
|
766
|
+
mueller = lib.check_input(
|
|
767
|
+
"mueller", mueller, shape=(n, 4), fill=np.array([1, 1, 0, 1])
|
|
768
|
+
)
|
|
769
|
+
|
|
770
|
+
ns = self._point.contents.n
|
|
771
|
+
shape = (n, ns)
|
|
772
|
+
|
|
773
|
+
if tod is not None:
|
|
774
|
+
tod = np.atleast_2d(tod)
|
|
775
|
+
|
|
776
|
+
if write:
|
|
777
|
+
tod = lib.check_output("tod", tod, shape=shape, fill=0)
|
|
778
|
+
self.depo["tod"] = tod
|
|
779
|
+
elif tod is not None:
|
|
780
|
+
tod = lib.check_input("tod", tod, shape=shape)
|
|
781
|
+
self.depo["tod"] = tod
|
|
782
|
+
if flag is not None:
|
|
783
|
+
flag = lib.check_input(
|
|
784
|
+
"flag", np.atleast_2d(flag), dtype=np.uint8, shape=shape
|
|
785
|
+
)
|
|
786
|
+
self.depo["flag"] = flag
|
|
787
|
+
if weights is not None:
|
|
788
|
+
weights = lib.check_input("weights", np.atleast_2d(weights), shape=shape)
|
|
789
|
+
self.depo["weights"] = weights
|
|
790
|
+
|
|
791
|
+
# populate array
|
|
792
|
+
dets = (lib.qp_det_t * n)()
|
|
793
|
+
for idx, (q, w, g, m) in enumerate(zip(q_off, weight, gain, mueller)):
|
|
794
|
+
dets[idx].init = lib.QP_STRUCT_INIT
|
|
795
|
+
dets[idx].q_off = lib.as_ctypes(q)
|
|
796
|
+
dets[idx].weight = w
|
|
797
|
+
dets[idx].gain = g
|
|
798
|
+
dets[idx].mueller = lib.as_ctypes(m)
|
|
799
|
+
if tod is not None:
|
|
800
|
+
dets[idx].n = ns
|
|
801
|
+
dets[idx].tod_init = lib.QP_ARR_INIT_PTR
|
|
802
|
+
dets[idx].tod = lib.as_ctypes(tod[idx])
|
|
803
|
+
else:
|
|
804
|
+
dets[idx].tod_init = 0
|
|
805
|
+
if flag is not None:
|
|
806
|
+
dets[idx].n = ns
|
|
807
|
+
dets[idx].flag_init = lib.QP_ARR_INIT_PTR
|
|
808
|
+
dets[idx].flag = lib.as_ctypes(flag[idx])
|
|
809
|
+
else:
|
|
810
|
+
dets[idx].flag_init = 0
|
|
811
|
+
if weights is not None:
|
|
812
|
+
dets[idx].n = ns
|
|
813
|
+
dets[idx].weights_init = lib.QP_ARR_INIT_PTR
|
|
814
|
+
dets[idx].weights = lib.as_ctypes(weights[idx])
|
|
815
|
+
|
|
816
|
+
detarr = lib.qp_detarr_t()
|
|
817
|
+
detarr.n = n
|
|
818
|
+
detarr.init = lib.QP_STRUCT_INIT
|
|
819
|
+
detarr.arr_init = lib.QP_ARR_INIT_PTR
|
|
820
|
+
detarr.arr = dets
|
|
821
|
+
detarr.diff = 0
|
|
822
|
+
|
|
823
|
+
if do_diff:
|
|
824
|
+
detarr.diff = 1
|
|
825
|
+
|
|
826
|
+
self._detarr = ct.pointer(detarr)
|
|
827
|
+
|
|
828
|
+
def reset_detarr(self):
|
|
829
|
+
"""
|
|
830
|
+
Reset the detector array structure.
|
|
831
|
+
"""
|
|
832
|
+
if hasattr(self, "_detarr") and self._detarr is not None:
|
|
833
|
+
qp.qp_free_detarr(self._detarr)
|
|
834
|
+
self._detarr = None
|
|
835
|
+
self.depo.pop("tod", None)
|
|
836
|
+
self.depo.pop("flag", None)
|
|
837
|
+
self.depo.pop("weights", None)
|
|
838
|
+
|
|
839
|
+
def reset(self):
|
|
840
|
+
"""
|
|
841
|
+
Reset the internal data structures, and clear the data depo.
|
|
842
|
+
"""
|
|
843
|
+
if not hasattr(self, "depo"):
|
|
844
|
+
self.depo = dict()
|
|
845
|
+
self.reset_source()
|
|
846
|
+
self.reset_dest()
|
|
847
|
+
self.reset_point()
|
|
848
|
+
self.reset_detarr()
|
|
849
|
+
self.depo.clear()
|
|
850
|
+
|
|
851
|
+
def from_tod(
|
|
852
|
+
self,
|
|
853
|
+
q_off,
|
|
854
|
+
tod=None,
|
|
855
|
+
count_hits=True,
|
|
856
|
+
weight=None,
|
|
857
|
+
gain=None,
|
|
858
|
+
mueller=None,
|
|
859
|
+
flag=None,
|
|
860
|
+
weights=None,
|
|
861
|
+
do_diff=False,
|
|
862
|
+
**kwargs,
|
|
863
|
+
):
|
|
864
|
+
"""
|
|
865
|
+
Calculate signal and hits maps for given detectors.
|
|
866
|
+
|
|
867
|
+
Arguments
|
|
868
|
+
---------
|
|
869
|
+
q_off : array_like
|
|
870
|
+
quaternion offset array, of shape (ndet, 4)
|
|
871
|
+
tod : array_like, optional
|
|
872
|
+
output array for timestreams, of shape (ndet, nsamp)
|
|
873
|
+
if not supplied, only the projection map is populated.
|
|
874
|
+
count_hits : bool, optional
|
|
875
|
+
if True (default), populate projection map.
|
|
876
|
+
weight : array_like, optional
|
|
877
|
+
array of channel weights, of shape (ndet,). Defaults to 1 if not
|
|
878
|
+
supplied.
|
|
879
|
+
gain : array_like, optional
|
|
880
|
+
Per-channel gains, of shape (ndet,) or a constant.
|
|
881
|
+
Default : 1.
|
|
882
|
+
mueller : array_like, optional
|
|
883
|
+
array of Mueller matrix A/B/C elements, of shape (ndet,3). Defaults to
|
|
884
|
+
[1, 1, 0] per channel if not supplied.
|
|
885
|
+
flag : array_like, optional
|
|
886
|
+
array of flag timestreams for each channel, of shape (ndet, nsamp).
|
|
887
|
+
weights : array_like, optional
|
|
888
|
+
array of weight timestreams for each channel, of shape (ndet, nsamp).
|
|
889
|
+
do_diff: do timestream differencing. Assumes first half of tods are
|
|
890
|
+
one pair and the second half are the other.
|
|
891
|
+
|
|
892
|
+
Returns
|
|
893
|
+
-------
|
|
894
|
+
vec : array_like, optional
|
|
895
|
+
binned signal map, if tod is supplied
|
|
896
|
+
proj : array_like, optional
|
|
897
|
+
binned projection matrix map, if count_hits is True
|
|
898
|
+
|
|
899
|
+
Notes
|
|
900
|
+
-----
|
|
901
|
+
The remaining keyword arguments are passed to the
|
|
902
|
+
:meth:`~qpoint.qpoint_class.QPoint.set` method.
|
|
903
|
+
"""
|
|
904
|
+
|
|
905
|
+
self.set(**kwargs)
|
|
906
|
+
|
|
907
|
+
# initialize detectors
|
|
908
|
+
self.init_detarr(
|
|
909
|
+
q_off,
|
|
910
|
+
weight=weight,
|
|
911
|
+
gain=gain,
|
|
912
|
+
mueller=mueller,
|
|
913
|
+
tod=tod,
|
|
914
|
+
flag=flag,
|
|
915
|
+
weights=weights,
|
|
916
|
+
do_diff=do_diff,
|
|
917
|
+
)
|
|
918
|
+
|
|
919
|
+
# check modes
|
|
920
|
+
return_vec = True
|
|
921
|
+
if tod is None or tod is False or self.depo["vec"] is False:
|
|
922
|
+
return_vec = False
|
|
923
|
+
return_proj = True
|
|
924
|
+
if not count_hits or self.depo["proj"] is False:
|
|
925
|
+
if return_vec is False:
|
|
926
|
+
raise RuntimeError("Nothing to do")
|
|
927
|
+
return_proj = False
|
|
928
|
+
|
|
929
|
+
# cache modes
|
|
930
|
+
dest = self._dest.contents
|
|
931
|
+
vec_mode = dest.vec_mode
|
|
932
|
+
if return_vec is False:
|
|
933
|
+
dest.vec_mode = 0
|
|
934
|
+
proj_mode = dest.proj_mode
|
|
935
|
+
if return_proj is False:
|
|
936
|
+
dest.proj_mode = 0
|
|
937
|
+
|
|
938
|
+
# run
|
|
939
|
+
if qp.qp_tod2map(self._memory, self._detarr, self._point, self._dest):
|
|
940
|
+
raise RuntimeError(qp.qp_get_error_string(self._memory))
|
|
941
|
+
|
|
942
|
+
# reset modes
|
|
943
|
+
dest.vec_mode = vec_mode
|
|
944
|
+
dest.proj_mode = proj_mode
|
|
945
|
+
|
|
946
|
+
# clean up
|
|
947
|
+
self.reset_detarr()
|
|
948
|
+
|
|
949
|
+
# return
|
|
950
|
+
ret = ()
|
|
951
|
+
if return_vec:
|
|
952
|
+
ret += (self.depo["vec"].squeeze(),)
|
|
953
|
+
if return_proj:
|
|
954
|
+
ret += (self.depo["proj"].squeeze(),)
|
|
955
|
+
if len(ret) == 1:
|
|
956
|
+
return ret[0]
|
|
957
|
+
return ret
|
|
958
|
+
|
|
959
|
+
def to_tod(self, q_off, gain=None, mueller=None, tod=None, flag=None, **kwargs):
|
|
960
|
+
"""
|
|
961
|
+
Calculate signal TOD from source map for multiple channels.
|
|
962
|
+
|
|
963
|
+
Arguments
|
|
964
|
+
---------
|
|
965
|
+
q_off : array_like
|
|
966
|
+
quaternion offset array, of shape (ndet, 4)
|
|
967
|
+
gain : array_like, optional
|
|
968
|
+
Per-channel gains, of shape (ndet,) or a constant.
|
|
969
|
+
Default : 1.
|
|
970
|
+
mueller : array_like, optional
|
|
971
|
+
array of Mueller matrix A/B/C/D elements, of shape (ndet, 4). Defaults t
|
|
972
|
+
[1, 1, 0, 1] per channel if not supplied.
|
|
973
|
+
tod : array_like, optional
|
|
974
|
+
output array for timestreams, of shape (ndet, nsamp)
|
|
975
|
+
use this keyword argument for in-place computation.
|
|
976
|
+
|
|
977
|
+
Returns
|
|
978
|
+
-------
|
|
979
|
+
tod : array_like
|
|
980
|
+
A timestream sampled from the input map for each requested detector.
|
|
981
|
+
The output array shape is (ndet, nsamp).
|
|
982
|
+
|
|
983
|
+
Notes
|
|
984
|
+
-----
|
|
985
|
+
The remaining keyword arguments are passed to the
|
|
986
|
+
:meth:`~qpoint.qpoint_class.QPoint.set` method.
|
|
987
|
+
"""
|
|
988
|
+
|
|
989
|
+
self.set(**kwargs)
|
|
990
|
+
|
|
991
|
+
# initialize detectors
|
|
992
|
+
self.init_detarr(
|
|
993
|
+
q_off, gain=gain, mueller=mueller, tod=tod, flag=flag, write=True
|
|
994
|
+
)
|
|
995
|
+
|
|
996
|
+
# run
|
|
997
|
+
if qp.qp_map2tod(self._memory, self._detarr, self._point, self._source):
|
|
998
|
+
raise RuntimeError(qp.qp_get_error_string(self._memory))
|
|
999
|
+
tod = self.depo.pop("tod")
|
|
1000
|
+
|
|
1001
|
+
# clean up
|
|
1002
|
+
self.reset_detarr()
|
|
1003
|
+
|
|
1004
|
+
# return
|
|
1005
|
+
return tod
|
|
1006
|
+
|
|
1007
|
+
def proj_cond(self, proj=None, mode=None, partial=False):
|
|
1008
|
+
"""
|
|
1009
|
+
Hits-normalized projection matrix condition number for
|
|
1010
|
+
each pixel.
|
|
1011
|
+
|
|
1012
|
+
Arguments
|
|
1013
|
+
---------
|
|
1014
|
+
proj : array_like
|
|
1015
|
+
Projection matrix, of shape (N*(N+1)/2, npix).
|
|
1016
|
+
If None, obtained from the depo.
|
|
1017
|
+
mode : {None, 1, -1, 2, -2, inf, -inf, 'fro'}, optional
|
|
1018
|
+
condition number order. See `numpy.linalg.cond`.
|
|
1019
|
+
Default: None (2-norm from SVD)
|
|
1020
|
+
partial : bool, optional
|
|
1021
|
+
If True, the map is not checked to ensure a proper healpix nside.
|
|
1022
|
+
|
|
1023
|
+
Returns
|
|
1024
|
+
-------
|
|
1025
|
+
cond : array_like
|
|
1026
|
+
Condition number of each pixel.
|
|
1027
|
+
"""
|
|
1028
|
+
|
|
1029
|
+
# check inputs
|
|
1030
|
+
if proj is None:
|
|
1031
|
+
proj = self.depo["proj"]
|
|
1032
|
+
if proj is None or proj is False:
|
|
1033
|
+
raise ValueError("missing proj")
|
|
1034
|
+
proj, _, nmap = check_proj(proj, copy=True, partial=partial)
|
|
1035
|
+
nproj = len(proj)
|
|
1036
|
+
|
|
1037
|
+
# normalize
|
|
1038
|
+
m = proj[0].astype(bool)
|
|
1039
|
+
proj[:, m] /= proj[0, m]
|
|
1040
|
+
proj[:, ~m] = np.inf
|
|
1041
|
+
|
|
1042
|
+
# return if unpolarized
|
|
1043
|
+
if nmap == 1:
|
|
1044
|
+
return proj[0]
|
|
1045
|
+
|
|
1046
|
+
# projection matrix indices
|
|
1047
|
+
idx = np.zeros((nmap, nmap), dtype=int)
|
|
1048
|
+
rtri, ctri = np.triu_indices(nmap)
|
|
1049
|
+
idx[rtri, ctri] = idx[ctri, rtri] = np.arange(nproj)
|
|
1050
|
+
|
|
1051
|
+
# calculate for each pixel
|
|
1052
|
+
proj[:, ~m] = 0
|
|
1053
|
+
cond = np.linalg.cond(proj[idx].transpose(2, 0, 1), p=mode)
|
|
1054
|
+
cond[~m] = np.inf
|
|
1055
|
+
# threshold at machine precision
|
|
1056
|
+
cond[cond > 1.0 / np.finfo(float).eps] = np.inf
|
|
1057
|
+
return cond
|
|
1058
|
+
|
|
1059
|
+
def solve_map(
|
|
1060
|
+
self,
|
|
1061
|
+
vec=None,
|
|
1062
|
+
proj=None,
|
|
1063
|
+
mask=None,
|
|
1064
|
+
copy=True,
|
|
1065
|
+
return_proj=False,
|
|
1066
|
+
return_mask=False,
|
|
1067
|
+
partial=None,
|
|
1068
|
+
fill=0,
|
|
1069
|
+
cond=None,
|
|
1070
|
+
cond_thresh=1e6,
|
|
1071
|
+
method="exact",
|
|
1072
|
+
):
|
|
1073
|
+
"""
|
|
1074
|
+
Solve for a map, given the binned map and the projection matrix
|
|
1075
|
+
for each pixel.
|
|
1076
|
+
|
|
1077
|
+
Arguments
|
|
1078
|
+
---------
|
|
1079
|
+
vec : array_like, optional
|
|
1080
|
+
A map or list of N maps. Default to the `"vec"` entry in
|
|
1081
|
+
:attr:`depo`.
|
|
1082
|
+
proj : array_like, optional
|
|
1083
|
+
An array of upper-triangular projection matrices for each pixel, of
|
|
1084
|
+
shape (N*(N+1)/2, npix). Default to the `"proj"` entry in
|
|
1085
|
+
:attr:`depo`.
|
|
1086
|
+
mask : array_like, optional
|
|
1087
|
+
A mask of shape (npix,), evaluates to True where pixels are valid.
|
|
1088
|
+
The input mask in converted to a boolean array if supplied.
|
|
1089
|
+
copy : bool, optional
|
|
1090
|
+
if False, do the computation in-place so that the input maps are
|
|
1091
|
+
modified. Otherwise, a copy is created prior to solving.
|
|
1092
|
+
Default: False.
|
|
1093
|
+
return_proj : bool, optional
|
|
1094
|
+
if True, return the Cholesky-decomposed projection matrix.
|
|
1095
|
+
if False, and inplace is True, the input projection matrix
|
|
1096
|
+
is not modified.
|
|
1097
|
+
return_mask : bool, optional
|
|
1098
|
+
if True, return the mask array, updated with any pixels
|
|
1099
|
+
that could not be solved.
|
|
1100
|
+
partial : bool, optional
|
|
1101
|
+
If True, the map is not checked to ensure a proper healpix nside.
|
|
1102
|
+
fill : scalar, optional
|
|
1103
|
+
Fill the solved map where proj == 0 with this value. Default: 0.
|
|
1104
|
+
cond : array_like, optional
|
|
1105
|
+
A map of condition number per pixel. If not supplied, this will be
|
|
1106
|
+
calculated using :meth:`proj_cond`
|
|
1107
|
+
cond_thresh : scalar, optional
|
|
1108
|
+
A threshold to place on the condition number to exclude pixels
|
|
1109
|
+
prior to solving. Reduce this to avoid `LinAlgError` due to
|
|
1110
|
+
singular matrices.
|
|
1111
|
+
method : string, optional
|
|
1112
|
+
Map inversion method. If "exact", invert the pointing matrix directly
|
|
1113
|
+
If "cho", use Cholesky decomposition to solve. Default: "exact".
|
|
1114
|
+
|
|
1115
|
+
Returns
|
|
1116
|
+
-------
|
|
1117
|
+
map : array_like
|
|
1118
|
+
A solved map or set of maps, in shape (N, npix).
|
|
1119
|
+
proj_out : array_like
|
|
1120
|
+
The upper triangular elements of the decomposed projection matrix,
|
|
1121
|
+
(if method is 'cho') or of the matrix inverse (if method is 'exact'),
|
|
1122
|
+
if requested, in shape (N*(N+1)/2, npix).
|
|
1123
|
+
mask : array_like
|
|
1124
|
+
1-d array, True for valid pixels, if `return_mask` is True
|
|
1125
|
+
"""
|
|
1126
|
+
|
|
1127
|
+
# check if we're dealing with a partial map
|
|
1128
|
+
if partial is None:
|
|
1129
|
+
if vec is None and "dest_pixels" in self.depo:
|
|
1130
|
+
partial = True
|
|
1131
|
+
else:
|
|
1132
|
+
partial = False
|
|
1133
|
+
|
|
1134
|
+
# ensure properly shaped arrays
|
|
1135
|
+
if vec is None:
|
|
1136
|
+
vec = self.depo["vec"]
|
|
1137
|
+
if vec is None or vec is False:
|
|
1138
|
+
raise ValueError("missing vec")
|
|
1139
|
+
vec, nside = check_map(vec, copy=copy, partial=partial)
|
|
1140
|
+
|
|
1141
|
+
if proj is None:
|
|
1142
|
+
proj = self.depo["proj"]
|
|
1143
|
+
if proj is None or proj is False:
|
|
1144
|
+
raise ValueError("missing proj")
|
|
1145
|
+
pcopy = True if not return_proj else copy
|
|
1146
|
+
proj, pnside, nmap = check_proj(proj, copy=pcopy, partial=partial)
|
|
1147
|
+
|
|
1148
|
+
if pnside != nside or nmap != len(vec):
|
|
1149
|
+
raise ValueError("vec and proj have incompatible shapes")
|
|
1150
|
+
nproj = len(proj)
|
|
1151
|
+
|
|
1152
|
+
# deal with mask
|
|
1153
|
+
if mask is None:
|
|
1154
|
+
mask = np.ones(len(vec[0]), dtype=bool)
|
|
1155
|
+
else:
|
|
1156
|
+
mcopy = True if not return_mask else copy
|
|
1157
|
+
mask, mnside = check_map(mask, copy=mcopy, partial=partial)
|
|
1158
|
+
if mnside != nside:
|
|
1159
|
+
raise ValueError("mask has incompatible shape")
|
|
1160
|
+
mask = mask.squeeze().astype(bool)
|
|
1161
|
+
mask &= proj[0].astype(bool)
|
|
1162
|
+
|
|
1163
|
+
# if unpolarized, just divide
|
|
1164
|
+
if len(vec) == 1:
|
|
1165
|
+
vec = vec.squeeze()
|
|
1166
|
+
proj = proj.squeeze()
|
|
1167
|
+
vec[mask] /= proj[mask]
|
|
1168
|
+
vec[~mask] = fill
|
|
1169
|
+
ret = (vec,) + (proj,) * return_proj + (mask,) * return_mask
|
|
1170
|
+
if len(ret) == 1:
|
|
1171
|
+
return ret[0]
|
|
1172
|
+
return ret
|
|
1173
|
+
|
|
1174
|
+
# projection matrix indices
|
|
1175
|
+
idx = np.zeros((nmap, nmap), dtype=int)
|
|
1176
|
+
rtri, ctri = np.triu_indices(nmap)
|
|
1177
|
+
idx[rtri, ctri] = idx[ctri, rtri] = np.arange(nproj)
|
|
1178
|
+
|
|
1179
|
+
# solve
|
|
1180
|
+
if method == "exact":
|
|
1181
|
+
if cond is None:
|
|
1182
|
+
cond = self.proj_cond(proj=proj, partial=partial)
|
|
1183
|
+
mask &= cond < cond_thresh
|
|
1184
|
+
vec[:, ~mask] = 0
|
|
1185
|
+
proj[..., ~mask] = np.eye(nmap)[rtri, ctri][:, None]
|
|
1186
|
+
# numpy 2 requires b (ie vec) to have shape (..., M, K) not (..., M).
|
|
1187
|
+
# Use K = 1 with np.newaxis.
|
|
1188
|
+
vec[:] = np.linalg.solve(
|
|
1189
|
+
proj[idx].transpose(2, 0, 1), vec.transpose()[..., np.newaxis]
|
|
1190
|
+
)[..., 0].transpose()
|
|
1191
|
+
vec[:, ~mask] = fill
|
|
1192
|
+
proj[:, ~mask] = 0
|
|
1193
|
+
ret = (vec,) + return_proj * (proj,) + return_mask * (mask,)
|
|
1194
|
+
if len(ret) == 1:
|
|
1195
|
+
return ret[0]
|
|
1196
|
+
return ret
|
|
1197
|
+
|
|
1198
|
+
if method != "cho":
|
|
1199
|
+
raise ValueError("Unrecognized method {}".format(method))
|
|
1200
|
+
|
|
1201
|
+
# slow method, loop over pixels
|
|
1202
|
+
from scipy.linalg import cho_factor, cho_solve
|
|
1203
|
+
|
|
1204
|
+
for ii, (m, A, v) in enumerate(zip(mask, proj[idx].T, vec.T)):
|
|
1205
|
+
if not m:
|
|
1206
|
+
proj[:, ii] = 0
|
|
1207
|
+
vec[:, ii] = fill
|
|
1208
|
+
continue
|
|
1209
|
+
try:
|
|
1210
|
+
vec[:, ii] = cho_solve(cho_factor(A, False, True), v, True)
|
|
1211
|
+
except:
|
|
1212
|
+
mask[ii] = False
|
|
1213
|
+
proj[:, ii] = 0
|
|
1214
|
+
vec[:, ii] = fill
|
|
1215
|
+
else:
|
|
1216
|
+
proj[:, ii] = A[rtri, ctri]
|
|
1217
|
+
|
|
1218
|
+
# return
|
|
1219
|
+
ret = (vec,) + (proj,) * return_proj + (mask,) * return_mask
|
|
1220
|
+
if len(ret) == 1:
|
|
1221
|
+
return ret[0]
|
|
1222
|
+
return ret
|
|
1223
|
+
|
|
1224
|
+
def solve_map_cho(self, *args, **kwargs):
|
|
1225
|
+
"""
|
|
1226
|
+
Solve for a map, given the binned map and the projection matrix
|
|
1227
|
+
for each pixel, using Cholesky decomposition. This method
|
|
1228
|
+
uses the scipy.linalg.cho_factor and scipy.linalg.cho_solve
|
|
1229
|
+
functions internally.
|
|
1230
|
+
|
|
1231
|
+
Arguments
|
|
1232
|
+
---------
|
|
1233
|
+
vec : array_like, optional
|
|
1234
|
+
A map or list of N maps. Default to the `"vec"` entry in
|
|
1235
|
+
:attr:`depo`.
|
|
1236
|
+
proj : array_like, optional
|
|
1237
|
+
An array of upper-triangular projection matrices for each pixel, of
|
|
1238
|
+
shape (N*(N+1)/2, npix). Default to the `"proj"` entry in
|
|
1239
|
+
:attr:`depo`.
|
|
1240
|
+
mask : array_like, optional
|
|
1241
|
+
A mask of shape (npix,), evaluates to True where pixels are valid.
|
|
1242
|
+
The input mask in converted to a boolean array if supplied.
|
|
1243
|
+
copy : bool, optional
|
|
1244
|
+
if False, do the computation in-place so that the input maps are
|
|
1245
|
+
modified. Otherwise, a copy is created prior to solving.
|
|
1246
|
+
Default: False.
|
|
1247
|
+
return_proj : bool, optional
|
|
1248
|
+
if True, return the Cholesky-decomposed projection matrix.
|
|
1249
|
+
if False, and inplace is True, the input projection matrix
|
|
1250
|
+
is not modified.
|
|
1251
|
+
return_mask : bool, optional
|
|
1252
|
+
if True, return the mask array, updated with any pixels
|
|
1253
|
+
that could not be solved.
|
|
1254
|
+
partial : bool, optional
|
|
1255
|
+
If True, the map is not checked to ensure a proper healpix nside.
|
|
1256
|
+
fill : scalar, optional
|
|
1257
|
+
Fill the solved map where proj == 0 with this value. Default: 0.
|
|
1258
|
+
cond : array_like, optional
|
|
1259
|
+
A map of condition number per pixel. If not supplied, this will be
|
|
1260
|
+
calculated using :meth:`proj_cond`
|
|
1261
|
+
cond_thresh : scalar, optional
|
|
1262
|
+
A threshold to place on the condition number to exclude pixels
|
|
1263
|
+
prior to solving. Reduce this to avoid `LinAlgError` due to
|
|
1264
|
+
singular matrices.
|
|
1265
|
+
|
|
1266
|
+
Returns
|
|
1267
|
+
-------
|
|
1268
|
+
map : array_like
|
|
1269
|
+
A solved map or set of maps, in shape (N, npix).
|
|
1270
|
+
proj_out : array_like
|
|
1271
|
+
The upper triangular elements of the decomposed projection matrix,
|
|
1272
|
+
if requested, in shape (N*(N+1)/2, npix).
|
|
1273
|
+
mask : array_like
|
|
1274
|
+
1-d array, True for valid pixels, if `return_mask` is True
|
|
1275
|
+
"""
|
|
1276
|
+
kwargs["method"] = "cho"
|
|
1277
|
+
return self.solve_map(*args, **kwargs)
|
|
1278
|
+
|
|
1279
|
+
def unsolve_map(
|
|
1280
|
+
self,
|
|
1281
|
+
map_in,
|
|
1282
|
+
proj=None,
|
|
1283
|
+
mask=None,
|
|
1284
|
+
copy=True,
|
|
1285
|
+
return_proj=False,
|
|
1286
|
+
return_mask=False,
|
|
1287
|
+
partial=None,
|
|
1288
|
+
fill=0,
|
|
1289
|
+
):
|
|
1290
|
+
"""
|
|
1291
|
+
Invert the solved map to recover the binned vec array.
|
|
1292
|
+
|
|
1293
|
+
Arguments
|
|
1294
|
+
---------
|
|
1295
|
+
map_in : array_like
|
|
1296
|
+
A map or list of N maps.
|
|
1297
|
+
proj : array_like, optional
|
|
1298
|
+
An array of upper-triangular projection matrices for each pixel, of
|
|
1299
|
+
shape (N*(N+1)/2, npix). Default to the `"proj"` entry in
|
|
1300
|
+
:attr:`depo`.
|
|
1301
|
+
mask : array_like, optional
|
|
1302
|
+
A mask of shape (npix,), evaluates to True where pixels are valid.
|
|
1303
|
+
The input mask in converted to a boolean array if supplied.
|
|
1304
|
+
copy : bool, optional
|
|
1305
|
+
if False, do the computation in-place so that the input maps are
|
|
1306
|
+
modified. Otherwise, a copy is created prior to solving.
|
|
1307
|
+
Default: False.
|
|
1308
|
+
return_proj : bool, optional
|
|
1309
|
+
if True, return the Cholesky-decomposed projection matrix.
|
|
1310
|
+
if False, and inplace is True, the input projection matrix
|
|
1311
|
+
is not modified.
|
|
1312
|
+
return_mask : bool, optional
|
|
1313
|
+
if True, return the mask array, updated with any pixels
|
|
1314
|
+
that could not be solved.
|
|
1315
|
+
partial : bool, optional
|
|
1316
|
+
If True, the map is not checked to ensure a proper healpix nside.
|
|
1317
|
+
fill : scalar, optional
|
|
1318
|
+
Fill the solved map where proj == 0 with this value. Default: 0.
|
|
1319
|
+
|
|
1320
|
+
Returns
|
|
1321
|
+
-------
|
|
1322
|
+
vec : array_like
|
|
1323
|
+
A binned map or set of maps, in shape (N, npix).
|
|
1324
|
+
proj_out : array_like
|
|
1325
|
+
The upper triangular elements of the projection matrix,
|
|
1326
|
+
if requested, in shape (N*(N+1)/2, npix).
|
|
1327
|
+
mask : array_like
|
|
1328
|
+
1-d array, True for valid pixels, if `return_mask` is True
|
|
1329
|
+
"""
|
|
1330
|
+
|
|
1331
|
+
# check if we're dealing with a partial map
|
|
1332
|
+
if partial is None:
|
|
1333
|
+
partial = "dest_pixels" in self.depo
|
|
1334
|
+
|
|
1335
|
+
# ensure properly shaped arrays
|
|
1336
|
+
map_in, nside = check_map(map_in, copy=copy, partial=partial)
|
|
1337
|
+
|
|
1338
|
+
if proj is None:
|
|
1339
|
+
proj = self.depo["proj"]
|
|
1340
|
+
if proj is None or proj is False:
|
|
1341
|
+
raise ValueError("missing proj")
|
|
1342
|
+
pcopy = True if not return_proj else copy
|
|
1343
|
+
proj, pnside, nmap = check_proj(proj, copy=pcopy, partial=partial)
|
|
1344
|
+
|
|
1345
|
+
if pnside != nside or nmap != len(map_in):
|
|
1346
|
+
raise ValueError("map_in and proj have incompatible shapes")
|
|
1347
|
+
nproj = len(proj)
|
|
1348
|
+
|
|
1349
|
+
# deal with mask
|
|
1350
|
+
if mask is None:
|
|
1351
|
+
mask = np.ones(len(map_in[0]), dtype=bool)
|
|
1352
|
+
else:
|
|
1353
|
+
mcopy = True if not return_mask else copy
|
|
1354
|
+
mask, mnside = check_map(mask, copy=mcopy, partial=partial)
|
|
1355
|
+
if mnside != nside:
|
|
1356
|
+
raise ValueError("mask has incompatible shape")
|
|
1357
|
+
mask = mask.squeeze().astype(bool)
|
|
1358
|
+
mask &= proj[0].astype(bool)
|
|
1359
|
+
|
|
1360
|
+
if len(map_in) == 1:
|
|
1361
|
+
# if unpolarized, just multiply
|
|
1362
|
+
map_in = map_in.squeeze()
|
|
1363
|
+
proj = proj.squeeze()
|
|
1364
|
+
map_in[mask] *= proj[mask]
|
|
1365
|
+
map_in[~mask] = fill
|
|
1366
|
+
else:
|
|
1367
|
+
# projection matrix indices
|
|
1368
|
+
idx = np.zeros((nmap, nmap), dtype=int)
|
|
1369
|
+
rtri, ctri = np.triu_indices(nmap)
|
|
1370
|
+
idx[rtri, ctri] = idx[ctri, rtri] = np.arange(nproj)
|
|
1371
|
+
map_in[:] = np.einsum("ij...,j...->i...", proj[idx], map_in)
|
|
1372
|
+
map_in[:, ~mask] = fill
|
|
1373
|
+
|
|
1374
|
+
# return
|
|
1375
|
+
ret = (map_in,) + (proj,) * return_proj + (mask,) * return_mask
|
|
1376
|
+
if len(ret) == 1:
|
|
1377
|
+
return ret[0]
|
|
1378
|
+
return ret
|