orpheus-npcf 0.2.1__cp310-cp310-musllinux_1_2_x86_64.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.
orpheus/npcf_second.py ADDED
@@ -0,0 +1,620 @@
1
+ import numpy as np
2
+ from pathlib import Path
3
+ import copy
4
+
5
+ from .catalog import Catalog, ScalarTracerCatalog, SpinTracerCatalog
6
+ from .npcf_base import BinnedNPCF
7
+
8
+ __all__ = ["NNCorrelation", "GGCorrelation"]
9
+
10
+
11
+ ###############################
12
+ ## SECOND - ORDER STATISTICS ##
13
+ ###############################
14
+
15
+ class NNCorrelation(BinnedNPCF):
16
+ r"""Compute pair counts and (optionally) the projected angular clustering two-point correlation function.
17
+
18
+ Parameters
19
+ ----------
20
+ min_sep: float
21
+ The smallest distance of each vertex for which the NPCF is computed.
22
+ max_sep: float
23
+ The largest distance of each vertex for which the NPCF is computed.
24
+ shuffle_pix: int, optional
25
+ Choice of how to define centers of the cells in the spatial hash structure.
26
+ Defaults to ``1``, i.e. random positioning.
27
+ **kwargs
28
+ Passed to :class:`~orpheus.npcf_base.BinnedNPCF`.
29
+
30
+
31
+ Attributes
32
+ ----------
33
+ npair: numpy.ndarray
34
+ The number of unweighted pairs.
35
+ npair_cell: numpy.ndarray
36
+ The number cell-pairs.
37
+ xi: numpy.ndarray
38
+ The scalar two-point correlation function.
39
+
40
+ Notes
41
+ -----
42
+ - Inherits all other parameters and attributes from :class:`BinnedNPCF`.
43
+ - Additional child-specific parameters can be passed via ``kwargs``.
44
+
45
+ - Binning:
46
+ - Either ``nbinsr`` or ``binsize`` must be provided to fix the binning scheme.
47
+ - If both are provided, the parent class rules determine which takes precedence.
48
+
49
+ - Pixel hashing / grid setup:
50
+ - ``shuffle_pix=1`` is the default (random cell centers).
51
+ - This differs from shear-based correlation functions where another default may be used.
52
+
53
+ - Estimator:
54
+ The scalar correlation function ``xi`` is formed from the pair counts via the Landy-Szalay estimator
55
+
56
+ .. math::
57
+
58
+ \xi(r) = \frac{DD(r) - 2\,DR(r) + RR(r)}{RR(r)}.
59
+
60
+ """
61
+
62
+ def __init__(self, min_sep, max_sep, shuffle_pix=1, **kwargs):
63
+ super().__init__(order=2, spins=np.array([0,0], dtype=np.int32), n_cfs=1, min_sep=min_sep, max_sep=max_sep, shuffle_pix=shuffle_pix, **kwargs)
64
+ self.projection = None
65
+ self.projections_avail = [None]
66
+ self.nbinsz = None
67
+ self.nzcombis = None
68
+ self.npair = None
69
+ self.npair_cell = None
70
+ self.xi = None
71
+
72
+ # (Add here any newly implemented projections)
73
+ self._initprojections(self)
74
+
75
+ def saveinst(self, path_save, fname):
76
+
77
+ if not Path(path_save).is_dir():
78
+ raise ValueError('Path to directory does not exist.')
79
+
80
+ np.savez(path_save+fname,
81
+ nbinsz=self.nbinsz,
82
+ min_sep=self.min_sep,
83
+ max_sep=self.max_sep,
84
+ binsr=self.nbinsr,
85
+ method=self.method,
86
+ shuffle_pix=self.shuffle_pix,
87
+ tree_resos=self.tree_resos,
88
+ rmin_pixsize=self.rmin_pixsize,
89
+ resoshift_leafs=self.resoshift_leafs,
90
+ minresoind_leaf=self.minresoind_leaf,
91
+ maxresoind_leaf=self.maxresoind_leaf,
92
+ nthreads=self.nthreads,
93
+ bin_centers=self.bin_centers,
94
+ bin_centers_mean=self.bin_centers_mean,
95
+ xi=self.xi,
96
+ npair=self.npair,
97
+ npair_cell=self.npair_cell)
98
+
99
+ def __process_patches(self, cat, dotomo=True, do_dc=True, adjust_tree=False,
100
+ save_patchres=False, save_filebase="", keep_patchres=False):
101
+
102
+ if save_patchres:
103
+ if not Path(save_patchres).is_dir():
104
+ raise ValueError('Path to directory does not exist.')
105
+
106
+ for elp in range(cat.npatches):
107
+ if self._verbose_python:
108
+ print('Doing patch %i/%i'%(elp+1,cat.npatches))
109
+
110
+ # Compute statistics on patch
111
+ pcat = cat.frompatchind(elp)
112
+ pcorr = NNCorrelation(
113
+ min_sep=self.min_sep,
114
+ max_sep=self.max_sep,
115
+ nbinsr=self.nbinsr,
116
+ method=self.method,
117
+ shuffle_pix=self.shuffle_pix,
118
+ tree_resos=self.tree_resos,
119
+ rmin_pixsize=self.rmin_pixsize,
120
+ resoshift_leafs=self.resoshift_leafs,
121
+ minresoind_leaf=self.minresoind_leaf,
122
+ maxresoind_leaf=self.maxresoind_leaf,
123
+ nthreads=self.nthreads,
124
+ verbosity=self.verbosity)
125
+ pcorr.process(pcat, dotomo=dotomo, do_dc=do_dc)
126
+
127
+ # Update the total measurement
128
+ if elp == 0:
129
+ self.nbinsz = pcorr.nbinsz
130
+ self.nzcombis = pcorr.nzcombis
131
+ self.bin_centers = np.zeros_like(pcorr.bin_centers)
132
+ self.npair = np.zeros_like(pcorr.npair)
133
+ self.npair_cell = np.zeros_like(pcorr.npair_cell)
134
+ if keep_patchres:
135
+ centers_patches = np.zeros((cat.npatches, *pcorr.bin_centers.shape), dtype=pcorr.bin_centers.dtype)
136
+ npair_patches = np.zeros((cat.npatches, *pcorr.npair.shape), dtype=pcorr.npair.dtype)
137
+ npair_cell_patches = np.zeros((cat.npatches, *pcorr.npair_cell.shape), dtype=pcorr.npair_cell.dtype)
138
+ self.bin_centers += pcorr.npair*pcorr.bin_centers
139
+ self.npair += pcorr.npair
140
+ self.npair_cell += pcorr.npair_cell
141
+ if keep_patchres:
142
+ centers_patches[elp] += pcorr.bin_centers
143
+ npair_patches[elp] += pcorr.npair
144
+ npair_cell_patches[elp] += pcorr.npair_cell
145
+ if save_patchres:
146
+ pcorr.saveinst(save_patchres, save_filebase+'_patch%i'%elp)
147
+
148
+ # Finalize the measurement on the full footprint
149
+ self.bin_centers /= self.npair
150
+ self.bin_centers_mean = np.mean(self.bin_centers, axis=0)
151
+
152
+ if keep_patchres:
153
+ return centers_patches, npair_patches, npair_cell_patches
154
+
155
+ def process(self, cat, cat_random=None, dotomo=True, do_dc=True, adjust_tree=False,
156
+ save_patchres=False, save_filebase="", keep_patchres=False):
157
+ r"""
158
+ Compute NN pair counts for a catalog, and optionally the clustering 2PCF ``xi``.
159
+
160
+ If ``cat_random`` is provided, ``xi`` is computed using the Landy–Szalay estimator.
161
+ Otherwise only pair counts are computed.
162
+
163
+ Parameters
164
+ ----------
165
+ cat: orpheus.ScalarTracerCatalog
166
+ The (clustered) catalog for which the pair counts are computed
167
+ cat_random: orpheus.ScalarTracerCatalog, optional
168
+ A random catalog. If this is set, the clustering correlation function ``xi`` is computed.
169
+ dotomo: bool
170
+ Flag that decides whether the tomographic information in the catalog should be used. Defaults to `True`.
171
+ do_dc: bool
172
+ Flag that decides whether to double-count the pair counts. This will be required when looking at data-random pairs.
173
+ within a tomographic catalog. Defaults to `True`. In case ``xi`` is computed, this argument is internally set to `True`.
174
+ adjust_tree: bool
175
+ Overrides the original setup of the tree-approximations in the instance based on the nbar of the catalog.
176
+ Not implemented yet, therefore no effect. Has no effect yet. Defaults to `False`.
177
+ save_patchres: bool or str
178
+ If the catalog has been decomposed in patches, flag whether to save the NN measurements on the individual patches.
179
+ Note that the path needs to exist, otherwise a `ValueError` is raised. For a flat-sky catalog this parameter
180
+ has no effect. Defaults to `False`.
181
+ save_filebase: str
182
+ Base of the filenames in which the patches are saved. The full filename will be `<save_patchres>/<save_filebase>_patchxx.npz`.
183
+ Only has an effect if the catalog consists of multiple patches and `save_patchres` is not `False`.
184
+ keep_patchres: bool
185
+ If the catalog consists of multiple patches, returns all measurements on the patches. Defaults to `False`.
186
+ """
187
+
188
+ # If random catalog present, use the __compute_xi method
189
+ if cat_random is not None:
190
+ assert(isinstance(cat_random, ScalarTracerCatalog))
191
+ self.__compute_xi(cat, cat_random, dotomo=dotomo, adjust_tree=adjust_tree,
192
+ save_patchres=save_patchres, keep_patchres=keep_patchres, estimator="LS")
193
+ return
194
+
195
+ # Make sure that in case the catalog is spherical, it has been decomposed into patches
196
+ if cat.geometry == 'spherical' and cat.patchinds is None:
197
+ raise ValueError('Error: Spherical catalog needs to be first decomposed into patches using the Catalog._topatches method.')
198
+
199
+ # Catalog consist of multiple patches
200
+ if cat.patchinds is not None:
201
+ return self.__process_patches(cat, dotomo=dotomo, do_dc=do_dc, adjust_tree=adjust_tree,
202
+ save_patchres=save_patchres, save_filebase=save_filebase, keep_patchres=keep_patchres)
203
+ # Catalog does not consist of patches
204
+ else:
205
+ # Prechecks
206
+ self._checkcats(cat, self.spins)
207
+ if not dotomo:
208
+ self.nbinsz = 1
209
+ old_zbins = cat.zbins[:]
210
+ cat.zbins = np.zeros(cat.ngal, dtype=np.int32)
211
+ self.nzcombis = 1
212
+ else:
213
+ self.nbinsz = cat.nbinsz
214
+ zbins = cat.zbins
215
+ self.nzcombis = self.nbinsz*self.nbinsz
216
+
217
+ z2r = self.nbinsz*self.nbinsz*self.nbinsr
218
+ sz2r = (self.nbinsz*self.nbinsz, self.nbinsr)
219
+ bin_centers = np.zeros(z2r).astype(np.float64)
220
+ npair = np.zeros(z2r).astype(np.float64)
221
+ npair_cell = np.zeros(z2r).astype(np.int64)
222
+
223
+ cutfirst = np.int32(self.tree_resos[0]==0.)
224
+ mhash = cat.multihash(dpixs=self.tree_resos[cutfirst:], dpix_hash=self.tree_resos[-1],
225
+ shuffle=self.shuffle_pix, normed=False)
226
+ ngal_resos, pos1s, pos2s, weights, zbins, isinners, allfields, index_matchers, pixs_galind_bounds, pix_gals, dpixs1_true, dpixs2_true = mhash
227
+ weight_resos = np.concatenate(weights).astype(np.float64)
228
+ pos1_resos = np.concatenate(pos1s).astype(np.float64)
229
+ pos2_resos = np.concatenate(pos2s).astype(np.float64)
230
+ zbin_resos = np.concatenate(zbins).astype(np.int32)
231
+ isinner_resos = np.concatenate(isinners).astype(np.float64)
232
+ index_matcher = np.concatenate(index_matchers).astype(np.int32)
233
+ pixs_galind_bounds = np.concatenate(pixs_galind_bounds).astype(np.int32)
234
+ pix_gals = np.concatenate(pix_gals).astype(np.int32)
235
+ index_matcher_flat = np.argwhere(cat.index_matcher>-1).flatten()
236
+ nregions = len(index_matcher_flat)
237
+
238
+ args_treeresos = (np.int32(self.tree_nresos), np.int32(self.tree_nresos-cutfirst),
239
+ dpixs1_true.astype(np.float64), dpixs2_true.astype(np.float64), self.tree_redges,
240
+ np.int32(self.resoshift_leafs), np.int32(self.minresoind_leaf),
241
+ np.int32(self.maxresoind_leaf), np.array(ngal_resos, dtype=np.int32), )
242
+ args_resos = (isinner_resos, weight_resos, pos1_resos, pos2_resos, zbin_resos,
243
+ index_matcher, pixs_galind_bounds, pix_gals, )
244
+ args_hash = (np.float64(cat.pix1_start), np.float64(cat.pix1_d), np.int32(cat.pix1_n),
245
+ np.float64(cat.pix2_start), np.float64(cat.pix2_d), np.int32(cat.pix2_n),
246
+ np.int32(nregions), index_matcher_flat.astype(np.int32),)
247
+ args_binning = (np.float64(self.min_sep), np.float64(self.max_sep), np.int32(self.nbinsr), np.int32(do_dc), )
248
+ args_output = (bin_centers, npair, npair_cell, )
249
+ func = self.clib.alloc_nn_doubletree
250
+ args = (*args_treeresos,
251
+ np.int32(self.nbinsz),
252
+ *args_resos,
253
+ *args_hash,
254
+ *args_binning,
255
+ np.int32(self.nthreads),
256
+ np.int32(self._verbose_c)+np.int32(self._verbose_debug),
257
+ *args_output)
258
+
259
+ func(*args)
260
+
261
+ self.bin_centers = bin_centers.reshape(sz2r)
262
+ self.bin_centers_mean = np.mean(self.bin_centers, axis=0)
263
+ self.npair = npair.reshape(sz2r)
264
+ self.npair_cell = npair_cell.reshape(sz2r)
265
+ self.projection = None
266
+
267
+ if not dotomo:
268
+ cat.zbins = old_zbins
269
+
270
+ return
271
+
272
+ def __compute_xi(self, cat_data, cat_rand, dotomo=True, adjust_tree=False,
273
+ save_patchres=False, keep_patchres=False, estimator="LS"):
274
+
275
+ # Define joint tomographic bins across data and random catalog
276
+ zbins = np.zeros(cat_data.ngal + cat_rand.ngal, dtype=int)
277
+ zbins[:cat_data.ngal] += cat_data.zbins
278
+ zbins[cat_data.ngal:] += cat_data.nbinsz + cat_rand.zbins
279
+ if not dotomo:
280
+ zbins[:cat_data.ngal] = 0
281
+ zbins[cat_data.ngal:] = 1
282
+
283
+ # Define joint catalog by appending randoms to data. This means it will have nz_joint=2*nz_data ordered as
284
+ # Z_1=Z_1_data, ..., Z_nz=Z_nz_data, Z_nz+1=Z_1_rand, ..., Z_2nz=Z_nz_rand
285
+ joint_cat = ScalarTracerCatalog(
286
+ pos1=np.append(cat_data.pos1, cat_rand.pos1),
287
+ pos2=np.append(cat_data.pos2, cat_rand.pos2),
288
+ tracer=np.ones(cat_data.ngal + cat_rand.ngal),
289
+ geometry=cat_data.geometry,
290
+ units_pos1= cat_data.units_pos1,
291
+ units_pos2= cat_data.units_pos1,
292
+ zbins=zbins)
293
+
294
+ # In case of a spherical geometry, decompose the joint catalog in patches of the same target geometry as
295
+ # the geometry that was specified in the data catalog
296
+ if cat_data.geometry=="spherical":
297
+ joint_cat.topatches(npatches=cat_data.npatches,
298
+ patchextend_deg=cat_data.patchinds['info']['patchextend_deg'],
299
+ nside_hash=cat_data.patchinds['info']['nside_hash'],
300
+ method=cat_data.patchinds['info']['method'],
301
+ kmeanshp_maxiter=cat_data.patchinds['info']['kmeanshp_maxiter'],
302
+ kmeanshp_tol=cat_data.patchinds['info']['kmeanshp_tol'],
303
+ kmeanshp_randomstate=cat_data.patchinds['info']['kmeanshp_randomstate'],
304
+ healpix_nside=cat_data.patchinds['info']['healpix_nside'])
305
+
306
+ # Compute NN counts of joint catalog
307
+ self.process(cat=joint_cat, dotomo=True, do_dc=True, adjust_tree=adjust_tree,
308
+ save_patchres=save_patchres, keep_patchres=keep_patchres)
309
+
310
+ # Now infer all the tomographic dd,dr,rd,rr pairs pairs from the joint correlator
311
+ # From the z-binning of the joint catalog given above the 2pcf will have the block structure
312
+ # DD DR
313
+ # RD RR
314
+ # where each block is of shape (nz, nz) and the ordering of the indices is the same across all blocks.
315
+ _zshift = cat_data.nbinsz
316
+ _creshape = self.npair.reshape((2*_zshift, 2*_zshift, self.nbinsr))
317
+ dds = _creshape[:_zshift,:_zshift].reshape((_zshift*_zshift, self.nbinsr))
318
+ rrs = _creshape[_zshift:,_zshift:].reshape((_zshift*_zshift, self.nbinsr))
319
+ drs = _creshape[:_zshift,_zshift:].reshape((_zshift*_zshift, self.nbinsr))
320
+ rds = _creshape[_zshift:,:_zshift].reshape((_zshift*_zshift, self.nbinsr))
321
+
322
+ # Get number of galaxies per tomo bin
323
+ _, ngal_zdata = np.unique(cat_data.zbins, return_counts=True)
324
+ _, ngal_zrand = np.unique(cat_rand.zbins, return_counts=True)
325
+ ngal_zdata = ngal_zdata.astype(float)
326
+ ngal_zrand = ngal_zrand.astype(float)
327
+ # Get prefactors of LS estimator
328
+ ngal_zrand_second = np.outer(ngal_zrand,(ngal_zrand-1))
329
+ pref_DD = np.outer(ngal_zrand,(ngal_zrand-1))/np.outer(ngal_zdata,(ngal_zdata-1))
330
+ pref_DR, pref_RD = np.meshgrid(ngal_zrand/ngal_zdata,ngal_zrand/ngal_zdata)
331
+ pref_DD = pref_DD.flatten()[:, np.newaxis]
332
+ pref_DR = pref_DR.flatten()[:, np.newaxis]
333
+ pref_RD = pref_RD.flatten()[:, np.newaxis]
334
+
335
+ # Combine all pair counts to get 2pcf estimator
336
+ if estimator=="LS":
337
+ self.xi = pref_DD*dds/rrs - pref_DR*drs/rrs - pref_RD*rds/rrs + 1
338
+
339
+
340
+ def computeNap2(self, radii, tofile=False):
341
+ """ Computes second-order aperture statistics given the projected angular clustering correlation function.
342
+ Uses the Crittenden 2002 filter.
343
+ """
344
+
345
+ nap2 = np.zeros((self.xi.shape[0], len(radii)), dtype=float)
346
+ for elr, R in enumerate(radii):
347
+ thetared = self.bin_centers_mean[np.newaxis,:]/R
348
+ measure = (self.bin_edges[1:]-self.bin_edges[:-1])*self.bin_centers_mean/(R**2)
349
+ filt = (thetared**4-16*thetared**2+32)/(128) * np.exp(-thetared**2/4.)
350
+ nap2[:,elr] = np.sum(measure*filt*self.xi,axis=1)
351
+
352
+ return nap2
353
+
354
+
355
+ class GGCorrelation(BinnedNPCF):
356
+ r""" Compute second-order correlation functions of spin-2 fields.
357
+
358
+ Parameters
359
+ ----------
360
+ min_sep: float
361
+ The smallest distance of each vertex for which the NPCF is computed.
362
+ max_sep: float
363
+ The largest distance of each vertex for which the NPCF is computed.
364
+
365
+ Attributes
366
+ ----------
367
+ xip: numpy.ndarray
368
+ The ξ₊ correlation function.
369
+ xim: numpy.ndarray
370
+ The ξ₋ correlation function.
371
+ norm: numpy.ndarray
372
+ The number of weighted pairs.
373
+ npair: numpy.ndarray
374
+ The number of unweighted pairs.
375
+
376
+ Notes
377
+ -----
378
+ Inherits all other parameters and attributes from :class:`BinnedNPCF`.
379
+ Additional child-specific parameters can be passed via ``kwargs``.
380
+ Either ``nbinsr`` or ``binsize`` has to be provided to fix the binning scheme .
381
+ """
382
+
383
+ def __init__(self, min_sep, max_sep, **kwargs):
384
+ super().__init__(order=2, spins=np.array([2,2], dtype=np.int32), n_cfs=2, min_sep=min_sep, max_sep=max_sep, **kwargs)
385
+ self.projection = None
386
+ self.projections_avail = [None]
387
+ self.nbinsz = None
388
+ self.nzcombis = None
389
+ self.counts = None
390
+ self.xip = None
391
+ self.xim = None
392
+ self.norm = None
393
+ self.npair = None
394
+
395
+ # (Add here any newly implemented projections)
396
+ self._initprojections(self)
397
+
398
+ def saveinst(self, path_save, fname):
399
+
400
+ if not Path(path_save).is_dir():
401
+ raise ValueError('Path to directory does not exist.')
402
+
403
+ np.savez(path_save+fname,
404
+ nbinsz=self.nbinsz,
405
+ min_sep=self.min_sep,
406
+ max_sep=self.max_sep,
407
+ binsr=self.nbinsr,
408
+ method=self.method,
409
+ shuffle_pix=self.shuffle_pix,
410
+ tree_resos=self.tree_resos,
411
+ rmin_pixsize=self.rmin_pixsize,
412
+ resoshift_leafs=self.resoshift_leafs,
413
+ minresoind_leaf=self.minresoind_leaf,
414
+ maxresoind_leaf=self.maxresoind_leaf,
415
+ nthreads=self.nthreads,
416
+ bin_centers=self.bin_centers,
417
+ xip=self.xip,
418
+ xim=self.xim,
419
+ npair=self.npair,
420
+ norm=self.norm)
421
+
422
+ def __process_patches(self, cat, dotomo=True, do_dc=False, rotsignflip=False, apply_edge_correction=False, adjust_tree=False,
423
+ save_patchres=False, save_filebase="", keep_patchres=False):
424
+
425
+ if save_patchres:
426
+ if not Path(save_patchres).is_dir():
427
+ raise ValueError('Path to directory does not exist.')
428
+
429
+ for elp in range(cat.npatches):
430
+ if self._verbose_python:
431
+ print('Doing patch %i/%i'%(elp+1,cat.npatches))
432
+
433
+ # Compute statistics on patch
434
+ pcat = cat.frompatchind(elp,rotsignflip=rotsignflip)
435
+ pcorr = GGCorrelation(
436
+ min_sep=self.min_sep,
437
+ max_sep=self.max_sep,
438
+ nbinsr=self.nbinsr,
439
+ method=self.method,
440
+ shuffle_pix=self.shuffle_pix,
441
+ tree_resos=self.tree_resos,
442
+ rmin_pixsize=self.rmin_pixsize,
443
+ resoshift_leafs=self.resoshift_leafs,
444
+ minresoind_leaf=self.minresoind_leaf,
445
+ maxresoind_leaf=self.maxresoind_leaf,
446
+ nthreads=self.nthreads,
447
+ verbosity=self.verbosity)
448
+ pcorr.process(pcat, dotomo=dotomo, do_dc=do_dc)
449
+
450
+ # Update the total measurement
451
+ if elp == 0:
452
+ self.nbinsz = pcorr.nbinsz
453
+ self.nzcombis = pcorr.nzcombis
454
+ self.bin_centers = np.zeros_like(pcorr.bin_centers)
455
+ self.xip = np.zeros_like(pcorr.xip)
456
+ self.xim = np.zeros_like(pcorr.xim)
457
+ self.norm = np.zeros_like(pcorr.norm)
458
+ self.npair = np.zeros_like(pcorr.norm)
459
+ if keep_patchres:
460
+ centers_patches = np.zeros((cat.npatches, *pcorr.bin_centers.shape), dtype=pcorr.bin_centers.dtype)
461
+ xip_patches = np.zeros((cat.npatches, *pcorr.xip.shape), dtype=pcorr.xip.dtype)
462
+ xim_patches = np.zeros((cat.npatches, *pcorr.xim.shape), dtype=pcorr.xim.dtype)
463
+ norm_patches = np.zeros((cat.npatches, *pcorr.norm.shape), dtype=pcorr.norm.dtype)
464
+ npair_patches = np.zeros((cat.npatches, *pcorr.npair.shape), dtype=pcorr.npair.dtype)
465
+ self.bin_centers += pcorr.norm*pcorr.bin_centers
466
+ self.xip += pcorr.norm*pcorr.xip
467
+ self.xim += pcorr.norm*pcorr.xim
468
+ self.norm += pcorr.norm
469
+ self.npair += pcorr.npair
470
+ if keep_patchres:
471
+ centers_patches[elp] += pcorr.bin_centers
472
+ xip_patches[elp] += pcorr.xip
473
+ xim_patches[elp] += pcorr.xim
474
+ norm_patches[elp] += pcorr.norm
475
+ npair_patches[elp] += pcorr.npair
476
+ if save_patchres:
477
+ pcorr.saveinst(save_patchres, save_filebase+'_patch%i'%elp)
478
+
479
+ # Finalize the measurement on the full footprint
480
+ self.bin_centers /= self.norm
481
+ self.bin_centers_mean = np.mean(self.bin_centers, axis=0)
482
+ self.xip /= self.norm
483
+ self.xim /= self.norm
484
+ self.projection = "xipm"
485
+
486
+ if keep_patchres:
487
+ return centers_patches, xip_patches, xim_patches, norm_patches, npair_patches
488
+
489
+ def process(self, cat, dotomo=True, do_dc=False, rotsignflip=False, adjust_tree=False,
490
+ save_patchres=False, save_filebase="", keep_patchres=False):
491
+ r"""
492
+ Compute a shear 2PCF given a shape catalog
493
+
494
+ Parameters
495
+ ----------
496
+ cat: orpheus.SpinTracerCatalog
497
+ The shape catalog to process.
498
+ dotomo: bool
499
+ Flag that decides whether the tomographic information in the shape catalog should be used. Defaults to `True`.
500
+ do_dc: bool
501
+ Whether to double-count pair counts. This will have no impact on :math:`\xi_\pm`, but can
502
+ significantly reduce the amplitude of :math:`\xi_\times`. Defaults to `False`.
503
+ rotsignflip: bool
504
+ If the shape catalog is has been decomposed in patches, choose whether the rotation angle should be flipped.
505
+ For simulated data this was always ok to set to 'False`. Defaults to `False`.
506
+ adjust_tree: bool
507
+ Overrides the original setup of the tree-approximations in the instance based on the nbar of the shape catalog.
508
+ Not implemented yet, therefore no effect. Has no effect yet. Defaults to `False`
509
+ save_patchres: bool or str
510
+ If the shape catalog is has been decomposed in patches, flag whether to save the GG measurements on the individual patches.
511
+ Note that the path needs to exist, otherwise a `ValueError` is raised. For a flat-sky catalog this parameter
512
+ has no effect. Defaults to `False`
513
+ save_filebase: str
514
+ Base of the filenames in which the patches are saved. The full filename will be `<save_patchres>/<save_filebase>_patchxx.npz`.
515
+ Only has an effect if the shape catalog consists of multiple patches and `save_patchres` is not `False`.
516
+ keep_patchres: bool
517
+ If the catalog consists of multiple patches, returns all measurements on the patches. Defaults to `False`.
518
+ """
519
+
520
+ # Make sure that in case the catalog is spherical, it has been decomposed into patches
521
+ if cat.geometry == 'spherical' and cat.patchinds is None:
522
+ raise ValueError('Error: Spherical catalog needs to be first decomposed into patches using the Catalog._topatches method.')
523
+
524
+ # Catalog consist of multiple patches
525
+ if cat.patchinds is not None:
526
+ return self.__process_patches(cat, dotomo=dotomo, do_dc=do_dc, rotsignflip=rotsignflip, adjust_tree=adjust_tree,
527
+ save_patchres=save_patchres, save_filebase=save_filebase, keep_patchres=keep_patchres)
528
+ # Catalog does not consist of patches
529
+ else:
530
+ # Prechecks
531
+ self._checkcats(cat, self.spins)
532
+ if not dotomo:
533
+ self.nbinsz = 1
534
+ old_zbins = cat.zbins[:]
535
+ cat.zbins = np.zeros(cat.ngal, dtype=np.int32)
536
+ self.nzcombis = 1
537
+ else:
538
+ self.nbinsz = cat.nbinsz
539
+ zbins = cat.zbins
540
+ self.nzcombis = self.nbinsz*self.nbinsz
541
+
542
+ z2r = self.nbinsz*self.nbinsz*self.nbinsr
543
+ sz2r = (self.nbinsz*self.nbinsz, self.nbinsr)
544
+ bin_centers = np.zeros(z2r).astype(np.float64)
545
+ xip = np.zeros(z2r).astype(np.complex128)
546
+ xim = np.zeros(z2r).astype(np.complex128)
547
+ norm = np.zeros(z2r).astype(np.float64)
548
+ npair = np.zeros(z2r).astype(np.int64)
549
+
550
+ cutfirst = np.int32(self.tree_resos[0]==0.)
551
+ mhash = cat.multihash(dpixs=self.tree_resos[cutfirst:], dpix_hash=self.tree_resos[-1],
552
+ shuffle=self.shuffle_pix, w2field=True, normed=True)
553
+ ngal_resos, pos1s, pos2s, weights, zbins, isinners, allfields, index_matchers, pixs_galind_bounds, pix_gals, dpixs1_true, dpixs2_true = mhash
554
+ weight_resos = np.concatenate(weights).astype(np.float64)
555
+ pos1_resos = np.concatenate(pos1s).astype(np.float64)
556
+ pos2_resos = np.concatenate(pos2s).astype(np.float64)
557
+ zbin_resos = np.concatenate(zbins).astype(np.int32)
558
+ isinner_resos = np.concatenate(isinners).astype(np.float64)
559
+ e1_resos = np.concatenate([allfields[i][0] for i in range(len(allfields))]).astype(np.float64)
560
+ e2_resos = np.concatenate([allfields[i][1] for i in range(len(allfields))]).astype(np.float64)
561
+ index_matcher = np.concatenate(index_matchers).astype(np.int32)
562
+ pixs_galind_bounds = np.concatenate(pixs_galind_bounds).astype(np.int32)
563
+ pix_gals = np.concatenate(pix_gals).astype(np.int32)
564
+ index_matcher_flat = np.argwhere(cat.index_matcher>-1).flatten()
565
+ nregions = len(index_matcher_flat)
566
+
567
+ args_treeresos = (np.int32(self.tree_nresos), np.int32(self.tree_nresos-cutfirst),
568
+ dpixs1_true.astype(np.float64), dpixs2_true.astype(np.float64), self.tree_redges,
569
+ np.int32(self.resoshift_leafs), np.int32(self.minresoind_leaf),
570
+ np.int32(self.maxresoind_leaf), np.array(ngal_resos, dtype=np.int32), )
571
+ args_resos = (isinner_resos, weight_resos, pos1_resos, pos2_resos, e1_resos, e2_resos, zbin_resos,
572
+ index_matcher, pixs_galind_bounds, pix_gals, )
573
+ args_hash = (np.float64(cat.pix1_start), np.float64(cat.pix1_d), np.int32(cat.pix1_n),
574
+ np.float64(cat.pix2_start), np.float64(cat.pix2_d), np.int32(cat.pix2_n),
575
+ np.int32(nregions), index_matcher_flat.astype(np.int32),)
576
+ args_binning = (np.float64(self.min_sep), np.float64(self.max_sep), np.int32(self.nbinsr), np.int32(do_dc))
577
+ args_output = (bin_centers, xip, xim, norm, npair, )
578
+ func = self.clib.alloc_xipm_doubletree
579
+ args = (*args_treeresos,
580
+ np.int32(self.nbinsz),
581
+ *args_resos,
582
+ *args_hash,
583
+ *args_binning,
584
+ np.int32(self.nthreads),
585
+ np.int32(self._verbose_c)+np.int32(self._verbose_debug),
586
+ *args_output)
587
+
588
+ func(*args)
589
+
590
+ self.bin_centers = bin_centers.reshape(sz2r)
591
+ self.bin_centers_mean = np.mean(self.bin_centers, axis=0)
592
+ self.npair = npair.reshape(sz2r)
593
+ self.norm = norm.reshape(sz2r)
594
+ self.xip = xip.reshape(sz2r)
595
+ self.xim = xim.reshape(sz2r)
596
+ self.projection = "xipm"
597
+
598
+ if not dotomo:
599
+ cat.zbins = old_zbins
600
+
601
+
602
+ def computeMap2(self, radii, tofile=False):
603
+ """ Computes second-order aperture mass statistics given the shear correlation functions.
604
+ Uses the Crittenden 2002 filter.
605
+ """
606
+
607
+ Tp = lambda x: 1./128. * (x**4-16*x**2+32) * np.exp(-x**2/4.)
608
+ Tm = lambda x: 1./128. * (x**4) * np.exp(-x**2/4.)
609
+ result = np.zeros((4, self.nzcombis, len(radii)), dtype=float)
610
+ for elr, R in enumerate(radii):
611
+ thetared = self.bin_centers/R
612
+ pref = self.binsize*thetared**2/2.
613
+ t1 = np.sum(pref*(Tp(thetared)*self.xip + Tm(thetared)*self.xim), axis=1)
614
+ t2 = np.sum(pref*(Tp(thetared)*self.xip - Tm(thetared)*self.xim), axis=1)
615
+ result[0,:,elr] = t1.real # Map2
616
+ result[1,:,elr] = t1.imag # MapMx
617
+ result[2,:,elr] = t2.real # Mx2
618
+ result[3,:,elr] = t2.imag # MxMap (Difference from MapMx gives ~level of estimator uncertainty)
619
+
620
+ return result