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/__init__.py +9 -0
- orpheus/catalog.py +1216 -0
- orpheus/covariance.py +153 -0
- orpheus/direct.py +1091 -0
- orpheus/flat2dgrid.py +68 -0
- orpheus/npcf_base.py +766 -0
- orpheus/npcf_fourth.py +1716 -0
- orpheus/npcf_second.py +620 -0
- orpheus/npcf_third.py +1684 -0
- orpheus/orpheus_clib.cpython-310-x86_64-linux-gnu.so +0 -0
- orpheus/patchutils.py +369 -0
- orpheus/utils.py +198 -0
- orpheus_npcf-0.2.1.dist-info/METADATA +67 -0
- orpheus_npcf-0.2.1.dist-info/RECORD +19 -0
- orpheus_npcf-0.2.1.dist-info/WHEEL +5 -0
- orpheus_npcf-0.2.1.dist-info/licenses/LICENSE +674 -0
- orpheus_npcf-0.2.1.dist-info/sboms/auditwheel.cdx.json +1 -0
- orpheus_npcf-0.2.1.dist-info/top_level.txt +1 -0
- orpheus_npcf.libs/libgomp-8949ffbe.so.1.0.0 +0 -0
orpheus/npcf_third.py
ADDED
|
@@ -0,0 +1,1684 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from functools import reduce
|
|
3
|
+
from numba import jit, prange
|
|
4
|
+
from numba import config as nb_config
|
|
5
|
+
from numba import complex128 as nb_complex128
|
|
6
|
+
import operator
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from scipy.interpolate import interp1d
|
|
9
|
+
|
|
10
|
+
from .npcf_base import BinnedNPCF
|
|
11
|
+
|
|
12
|
+
__all__ = ["GGGCorrelation", "GNNCorrelation", "NGGCorrelation"]
|
|
13
|
+
|
|
14
|
+
class GGGCorrelation(BinnedNPCF):
|
|
15
|
+
r""" Class containing methods to measure and and obtain statistics that are built
|
|
16
|
+
from third-order shear correlation functions.
|
|
17
|
+
|
|
18
|
+
Attributes
|
|
19
|
+
----------
|
|
20
|
+
n_cfs: int
|
|
21
|
+
The number of independent components of the NPCF.
|
|
22
|
+
min_sep: float
|
|
23
|
+
The smallest distance of each vertex for which the NPCF is computed.
|
|
24
|
+
max_sep: float
|
|
25
|
+
The largest distance of each vertex for which the NPCF is computed.
|
|
26
|
+
|
|
27
|
+
Notes
|
|
28
|
+
-----
|
|
29
|
+
Inherits all other parameters and attributes from :class:`BinnedNPCF`.
|
|
30
|
+
Additional child-specific parameters can be passed via ``kwargs``.
|
|
31
|
+
Either ``nbinsr`` or ``binsize`` has to be provided to fix the binning scheme .
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, n_cfs, min_sep, max_sep, **kwargs):
|
|
35
|
+
|
|
36
|
+
super().__init__(order=3, spins=np.array([2,2,2], dtype=np.int32), n_cfs=n_cfs, min_sep=min_sep, max_sep=max_sep, **kwargs)
|
|
37
|
+
self.nmax = self.nmaxs[0]
|
|
38
|
+
self.phi = self.phis[0]
|
|
39
|
+
self.projection = None
|
|
40
|
+
self.projections_avail = [None, "X", "Centroid"]
|
|
41
|
+
self.nbinsz = None
|
|
42
|
+
self.nzcombis = None
|
|
43
|
+
|
|
44
|
+
# (Add here any newly implemented projections)
|
|
45
|
+
self._initprojections(self)
|
|
46
|
+
self.project["X"]["Centroid"] = self._x2centroid
|
|
47
|
+
|
|
48
|
+
def saveinst(self, path_save, fname):
|
|
49
|
+
|
|
50
|
+
if not Path(path_save).is_dir():
|
|
51
|
+
raise ValueError('Path to directory does not exist.')
|
|
52
|
+
|
|
53
|
+
np.savez(path_save+fname,
|
|
54
|
+
nbinsz=self.nbinsz,
|
|
55
|
+
min_sep=self.min_sep,
|
|
56
|
+
max_sep=self.max_sep,
|
|
57
|
+
binsr=self.nbinsr,
|
|
58
|
+
nbinsphi=self.nbinsphi,
|
|
59
|
+
nmaxs=self.nmaxs,
|
|
60
|
+
method=self.method,
|
|
61
|
+
multicountcorr=self.multicountcorr,
|
|
62
|
+
shuffle_pix=self.shuffle_pix,
|
|
63
|
+
tree_resos=self.tree_resos,
|
|
64
|
+
rmin_pixsize=self.rmin_pixsize,
|
|
65
|
+
resoshift_leafs=self.resoshift_leafs,
|
|
66
|
+
minresoind_leaf=self.minresoind_leaf,
|
|
67
|
+
maxresoind_leaf=self.maxresoind_leaf,
|
|
68
|
+
nthreads=self.nthreads,
|
|
69
|
+
bin_centers=self.bin_centers,
|
|
70
|
+
npcf_multipoles=self.npcf_multipoles,
|
|
71
|
+
npcf_multipoles_norm=self.npcf_multipoles_norm)
|
|
72
|
+
|
|
73
|
+
def __process_patches(self, cat, dotomo=True, rotsignflip=False, apply_edge_correction=False, adjust_tree=False,
|
|
74
|
+
save_patchres=False, save_filebase="", keep_patchres=False):
|
|
75
|
+
|
|
76
|
+
if save_patchres:
|
|
77
|
+
if not Path(save_patchres).is_dir():
|
|
78
|
+
raise ValueError('Path to directory does not exist.')
|
|
79
|
+
|
|
80
|
+
for elp in range(cat.npatches):
|
|
81
|
+
if self._verbose_python:
|
|
82
|
+
print('Doing patch %i/%i'%(elp+1,cat.npatches))
|
|
83
|
+
|
|
84
|
+
# Compute statistics on patch
|
|
85
|
+
pcat = cat.frompatchind(elp,rotsignflip=rotsignflip)
|
|
86
|
+
pcorr = GGGCorrelation(
|
|
87
|
+
n_cfs=self.n_cfs,
|
|
88
|
+
min_sep=self.min_sep,
|
|
89
|
+
max_sep=self.max_sep,
|
|
90
|
+
nbinsr=self.nbinsr,
|
|
91
|
+
nbinsphi=self.nbinsphi,
|
|
92
|
+
nmaxs=self.nmaxs,
|
|
93
|
+
method=self.method,
|
|
94
|
+
multicountcorr=self.multicountcorr,
|
|
95
|
+
shuffle_pix=self.shuffle_pix,
|
|
96
|
+
tree_resos=self.tree_resos,
|
|
97
|
+
rmin_pixsize=self.rmin_pixsize,
|
|
98
|
+
resoshift_leafs=self.resoshift_leafs,
|
|
99
|
+
minresoind_leaf=self.minresoind_leaf,
|
|
100
|
+
maxresoind_leaf=self.maxresoind_leaf,
|
|
101
|
+
nthreads=self.nthreads,
|
|
102
|
+
verbosity=self.verbosity)
|
|
103
|
+
pcorr.process(pcat, dotomo=dotomo)
|
|
104
|
+
|
|
105
|
+
# Update the total measurement
|
|
106
|
+
if elp == 0:
|
|
107
|
+
self.nbinsz = pcorr.nbinsz
|
|
108
|
+
self.nzcombis = pcorr.nzcombis
|
|
109
|
+
self.bin_centers = np.zeros_like(pcorr.bin_centers)
|
|
110
|
+
self.npcf_multipoles = np.zeros_like(pcorr.npcf_multipoles)
|
|
111
|
+
self.npcf_multipoles_norm = np.zeros_like(pcorr.npcf_multipoles_norm)
|
|
112
|
+
_footnorm = np.zeros_like(pcorr.bin_centers)
|
|
113
|
+
if keep_patchres:
|
|
114
|
+
centers_patches = np.zeros((cat.npatches, *pcorr.bin_centers.shape), dtype=pcorr.bin_centers.dtype)
|
|
115
|
+
npcf_multipoles_patches = np.zeros((cat.npatches, *pcorr.npcf_multipoles.shape), dtype=pcorr.npcf_multipoles.dtype)
|
|
116
|
+
npcf_multipoles_norm_patches = np.zeros((cat.npatches, *pcorr.npcf_multipoles_norm.shape), dtype=pcorr.npcf_multipoles_norm.dtype)
|
|
117
|
+
_shelltriplets = np.array([[pcorr.npcf_multipoles_norm[0,z*self.nbinsz*self.nbinsz+z*self.nbinsz+z,i,i].real
|
|
118
|
+
for i in range(pcorr.nbinsr)] for z in range(self.nbinsz)]) # Rough estimate of scaling of pair counts based on zeroth multipole of triplets
|
|
119
|
+
# Rough estimate of scaling of pair counts based on zeroth multipole of triplets. Note that we might get nans here due to numerical
|
|
120
|
+
# inaccuracies in the multiple counting corrections for bins with zero triplets, so we force those values to be zero.
|
|
121
|
+
_patchnorm = np.nan_to_num(np.sqrt(_shelltriplets))
|
|
122
|
+
self.bin_centers += _patchnorm*pcorr.bin_centers
|
|
123
|
+
_footnorm += _patchnorm
|
|
124
|
+
self.npcf_multipoles += pcorr.npcf_multipoles
|
|
125
|
+
self.npcf_multipoles_norm += pcorr.npcf_multipoles_norm
|
|
126
|
+
if keep_patchres:
|
|
127
|
+
centers_patches[elp] += pcorr.bin_centers
|
|
128
|
+
npcf_multipoles_patches[elp] += pcorr.npcf_multipoles
|
|
129
|
+
npcf_multipoles_norm_patches[elp] += pcorr.npcf_multipoles_norm
|
|
130
|
+
if save_patchres:
|
|
131
|
+
pcorr.saveinst(save_patchres, save_filebase+'_patch%i'%elp)
|
|
132
|
+
|
|
133
|
+
# Finalize the measurement on the full footprint
|
|
134
|
+
self.bin_centers = np.divide(self.bin_centers,_footnorm, out=np.zeros_like(self.bin_centers), where=_footnorm>0)
|
|
135
|
+
self.bin_centers_mean = np.mean(self.bin_centers,axis=0)
|
|
136
|
+
self.projection = "X"
|
|
137
|
+
|
|
138
|
+
if keep_patchres:
|
|
139
|
+
return centers_patches, npcf_multipoles_patches, npcf_multipoles_norm_patches
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def process(self, cat, dotomo=True, rotsignflip=False, apply_edge_correction=False, adjust_tree=False,
|
|
143
|
+
save_patchres=False, save_filebase="", keep_patchres=False):
|
|
144
|
+
r"""
|
|
145
|
+
Compute a shear 3PCF provided a shape catalog
|
|
146
|
+
|
|
147
|
+
Parameters
|
|
148
|
+
----------
|
|
149
|
+
cat: orpheus.SpinTracerCatalog
|
|
150
|
+
The shape catalog which is processed
|
|
151
|
+
dotomo: bool
|
|
152
|
+
Flag that decides whether the tomographic information in the shape catalog should be used. Defaults to `True`.
|
|
153
|
+
rotsignflip: bool
|
|
154
|
+
If the shape catalog is has been decomposed in patches, choose whether the rotation angle should be flipped.
|
|
155
|
+
For simulated data this was always ok to set to 'False`. Has no effect yet. Defaults to `False`.
|
|
156
|
+
apply_edge_correction: bool
|
|
157
|
+
Flag that decides how the NPCF in the real space basis is computed.
|
|
158
|
+
* If set to `True` the computation is done via edge-correcting the GGG-multipoles
|
|
159
|
+
* If set to `False` both GGG and NNN are transformed separately and the ratio is done in the real-space basis
|
|
160
|
+
Defaults to `False`.
|
|
161
|
+
adjust_tree: bool
|
|
162
|
+
Overrides the original setup of the tree-approximations in the instance based on the nbar of the shape catalog.
|
|
163
|
+
Not implemented yet, therefore no effect. Has no effect yet. Defaults to `False`
|
|
164
|
+
save_patchres: bool or str
|
|
165
|
+
If the shape catalog is has been decomposed in patches, flag whether to save the GGG measurements on the individual patches.
|
|
166
|
+
Note that the path needs to exist, otherwise a `ValueError` is raised. For a flat-sky catalog this parameter
|
|
167
|
+
has no effect. Defaults to `False`
|
|
168
|
+
save_filebase: str
|
|
169
|
+
Base of the filenames in which the patches are saved. The full filename will be `<save_patchres>/<save_filebase>_patchxx.npz`.
|
|
170
|
+
Only has an effect if the shape catalog consists of multiple patches and `save_patchres` is not `False`.
|
|
171
|
+
keep_patchres: bool
|
|
172
|
+
If the catalog consists of multiple patches, returns all measurements on the patches. Defaults to `False`.
|
|
173
|
+
"""
|
|
174
|
+
|
|
175
|
+
# Make sure that in case the catalog is spherical, it has been decomposed into patches
|
|
176
|
+
if cat.geometry == 'spherical' and cat.patchinds is None:
|
|
177
|
+
raise ValueError('Error: Spherical catalog needs to be first decomposed into patches using the Catalog._topatches method.')
|
|
178
|
+
|
|
179
|
+
# Catalog consist of multiple patches
|
|
180
|
+
if cat.patchinds is not None:
|
|
181
|
+
return self.__process_patches(cat, dotomo=dotomo, rotsignflip=rotsignflip,
|
|
182
|
+
apply_edge_correction=apply_edge_correction, adjust_tree=adjust_tree,
|
|
183
|
+
save_patchres=save_patchres, save_filebase=save_filebase, keep_patchres=keep_patchres)
|
|
184
|
+
|
|
185
|
+
# Catalog does not consist of patches
|
|
186
|
+
else:
|
|
187
|
+
self._checkcats(cat, self.spins)
|
|
188
|
+
if not dotomo:
|
|
189
|
+
self.nbinsz = 1
|
|
190
|
+
old_zbins = cat.zbins[:]
|
|
191
|
+
cat.zbins = np.zeros(cat.ngal, dtype=np.int32)
|
|
192
|
+
self.nzcombis = 1
|
|
193
|
+
else:
|
|
194
|
+
self.nbinsz = cat.nbinsz
|
|
195
|
+
zbins = cat.zbins
|
|
196
|
+
self.nzcombis = self.nbinsz*self.nbinsz*self.nbinsz
|
|
197
|
+
if adjust_tree:
|
|
198
|
+
nbar = cat.ngal/(cat.len1*cat.len2)
|
|
199
|
+
|
|
200
|
+
sc = (4,self.nmax+1,self.nzcombis,self.nbinsr,self.nbinsr)
|
|
201
|
+
sn = (self.nmax+1,self.nzcombis,self.nbinsr,self.nbinsr)
|
|
202
|
+
szr = (self.nbinsz, self.nbinsr)
|
|
203
|
+
bin_centers = np.zeros(self.nbinsz*self.nbinsr).astype(np.float64)
|
|
204
|
+
threepcfs_n = np.zeros(4*(self.nmax+1)*self.nzcombis*self.nbinsr*self.nbinsr).astype(np.complex128)
|
|
205
|
+
threepcfsnorm_n = np.zeros((self.nmax+1)*self.nzcombis*self.nbinsr*self.nbinsr).astype(np.complex128)
|
|
206
|
+
args_basecat = (cat.isinner.astype(np.float64), cat.weight, cat.pos1, cat.pos2, cat.tracer_1, cat.tracer_2,
|
|
207
|
+
cat.zbins.astype(np.int32), np.int32(self.nbinsz), np.int32(cat.ngal), )
|
|
208
|
+
args_basesetup = (np.int32(0), np.int32(self.nmax), np.float64(self.min_sep),
|
|
209
|
+
np.float64(self.max_sep), np.array([-1.]).astype(np.float64),
|
|
210
|
+
np.int32(self.nbinsr), np.int32(self.multicountcorr), )
|
|
211
|
+
if self.method=="Discrete":
|
|
212
|
+
if not cat.hasspatialhash:
|
|
213
|
+
cat.build_spatialhash(dpix=max(1.,self.max_sep//10.))
|
|
214
|
+
args_pixgrid = (np.float64(cat.pix1_start), np.float64(cat.pix1_d), np.int32(cat.pix1_n),
|
|
215
|
+
np.float64(cat.pix2_start), np.float64(cat.pix2_d), np.int32(cat.pix2_n), )
|
|
216
|
+
args = (*args_basecat,
|
|
217
|
+
*args_basesetup,
|
|
218
|
+
cat.index_matcher,
|
|
219
|
+
cat.pixs_galind_bounds,
|
|
220
|
+
cat.pix_gals,
|
|
221
|
+
*args_pixgrid,
|
|
222
|
+
np.int32(self.nthreads),
|
|
223
|
+
np.int32(self._verbose_c),
|
|
224
|
+
bin_centers,
|
|
225
|
+
threepcfs_n,
|
|
226
|
+
threepcfsnorm_n)
|
|
227
|
+
func = self.clib.alloc_Gammans_discrete_ggg
|
|
228
|
+
elif self.method in ["Tree", "BaseTree", "DoubleTree"]:
|
|
229
|
+
if self._verbose_debug:
|
|
230
|
+
print("Doing multihash")
|
|
231
|
+
cutfirst = np.int32(self.tree_resos[0]==0.)
|
|
232
|
+
mhash = cat.multihash(dpixs=self.tree_resos[cutfirst:], dpix_hash=self.tree_resos[-1],
|
|
233
|
+
shuffle=self.shuffle_pix, w2field=True, normed=True)
|
|
234
|
+
ngal_resos, pos1s, pos2s, weights, zbins, isinners, allfields, index_matchers, pixs_galind_bounds, pix_gals, dpixs1_true, dpixs2_true = mhash
|
|
235
|
+
weight_resos = np.concatenate(weights).astype(np.float64)
|
|
236
|
+
pos1_resos = np.concatenate(pos1s).astype(np.float64)
|
|
237
|
+
pos2_resos = np.concatenate(pos2s).astype(np.float64)
|
|
238
|
+
zbin_resos = np.concatenate(zbins).astype(np.int32)
|
|
239
|
+
isinner_resos = np.concatenate(isinners).astype(np.float64)
|
|
240
|
+
e1_resos = np.concatenate([allfields[i][0] for i in range(len(allfields))]).astype(np.float64)
|
|
241
|
+
e2_resos = np.concatenate([allfields[i][1] for i in range(len(allfields))]).astype(np.float64)
|
|
242
|
+
_weightsq_resos = np.concatenate([allfields[i][2] for i in range(len(allfields))]).astype(np.float64)
|
|
243
|
+
weightsq_resos = _weightsq_resos*weight_resos # As in reduce we renorm all the fields --> need to `unrenorm'
|
|
244
|
+
index_matcher = np.concatenate(index_matchers).astype(np.int32)
|
|
245
|
+
pixs_galind_bounds = np.concatenate(pixs_galind_bounds).astype(np.int32)
|
|
246
|
+
pix_gals = np.concatenate(pix_gals).astype(np.int32)
|
|
247
|
+
args_pixgrid = (np.float64(cat.pix1_start), np.float64(cat.pix1_d), np.int32(cat.pix1_n),
|
|
248
|
+
np.float64(cat.pix2_start), np.float64(cat.pix2_d), np.int32(cat.pix2_n), )
|
|
249
|
+
args_resos = (weight_resos, pos1_resos, pos2_resos, e1_resos, e2_resos, zbin_resos, weightsq_resos,
|
|
250
|
+
index_matcher, pixs_galind_bounds, pix_gals, )
|
|
251
|
+
args_output = (bin_centers, threepcfs_n, threepcfsnorm_n, )
|
|
252
|
+
if self._verbose_debug:
|
|
253
|
+
print("Doing %s"%self.method)
|
|
254
|
+
if self.method=="Tree":
|
|
255
|
+
args = (*args_basecat,
|
|
256
|
+
np.int32(self.tree_nresos),
|
|
257
|
+
self.tree_redges,
|
|
258
|
+
np.array(ngal_resos, dtype=np.int32),
|
|
259
|
+
*args_resos,
|
|
260
|
+
*args_pixgrid,
|
|
261
|
+
*args_basesetup,
|
|
262
|
+
np.int32(self.nthreads),
|
|
263
|
+
np.int32(self._verbose_c),
|
|
264
|
+
*args_output)
|
|
265
|
+
func = self.clib.alloc_Gammans_tree_ggg
|
|
266
|
+
if self.method in ["BaseTree", "DoubleTree"]:
|
|
267
|
+
args_resos = (isinner_resos, ) + args_resos
|
|
268
|
+
index_matcher_flat = np.argwhere(cat.index_matcher>-1).flatten()
|
|
269
|
+
nregions = len(index_matcher_flat)
|
|
270
|
+
# Select regions with at least one inner galaxy (TODO: Optimize)
|
|
271
|
+
filledregions = []
|
|
272
|
+
for elregion in range(nregions):
|
|
273
|
+
_ = cat.pix_gals[cat.pixs_galind_bounds[elregion]:cat.pixs_galind_bounds[elregion+1]]
|
|
274
|
+
if np.sum(cat.isinner[_])>0:filledregions.append(elregion)
|
|
275
|
+
filledregions = np.asarray(filledregions, dtype=np.int32)
|
|
276
|
+
nfilledregions = np.int32(len(filledregions))
|
|
277
|
+
args_regions = (index_matcher_flat.astype(np.int32), np.int32(nregions), filledregions, nfilledregions, )
|
|
278
|
+
args_basesetup_dtree = (np.int32(self.nmax), np.float64(self.min_sep), np.float64(self.max_sep),
|
|
279
|
+
np.int32(self.nbinsr), np.int32(self.multicountcorr), )
|
|
280
|
+
args_treeresos = (np.int32(self.tree_nresos), np.int32(self.tree_nresos-cutfirst),
|
|
281
|
+
dpixs1_true.astype(np.float64), dpixs2_true.astype(np.float64), self.tree_redges, )
|
|
282
|
+
if self.method=="BaseTree":
|
|
283
|
+
func = self.clib.alloc_Gammans_basetree_ggg
|
|
284
|
+
if self.method=="DoubleTree":
|
|
285
|
+
args_leafs = (np.int32(self.resoshift_leafs), np.int32(self.minresoind_leaf),
|
|
286
|
+
np.int32(self.maxresoind_leaf), )
|
|
287
|
+
args_treeresos = args_treeresos + args_leafs
|
|
288
|
+
func = self.clib.alloc_Gammans_doubletree_ggg
|
|
289
|
+
args = (*args_treeresos,
|
|
290
|
+
np.array(ngal_resos, dtype=np.int32),
|
|
291
|
+
np.int32(self.nbinsz),
|
|
292
|
+
*args_resos,
|
|
293
|
+
*args_pixgrid,
|
|
294
|
+
*args_regions,
|
|
295
|
+
*args_basesetup_dtree,
|
|
296
|
+
np.int32(self.nthreads),
|
|
297
|
+
np.int32(self._verbose_c),
|
|
298
|
+
*args_output)
|
|
299
|
+
func(*args)
|
|
300
|
+
|
|
301
|
+
self.bin_centers = bin_centers.reshape(szr)
|
|
302
|
+
self.bin_centers_mean = np.mean(self.bin_centers, axis=0)
|
|
303
|
+
self.npcf_multipoles = threepcfs_n.reshape(sc)
|
|
304
|
+
self.npcf_multipoles_norm = threepcfsnorm_n.reshape(sn)
|
|
305
|
+
self.projection = "X"
|
|
306
|
+
|
|
307
|
+
if apply_edge_correction:
|
|
308
|
+
self.edge_correction()
|
|
309
|
+
|
|
310
|
+
if not dotomo:
|
|
311
|
+
cat.zbins = old_zbins
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def edge_correction(self, ret_matrices=False):
|
|
315
|
+
|
|
316
|
+
def gen_M_matrix(thet1,thet2,threepcf_n_norm):
|
|
317
|
+
nvals, ntheta, _ = threepcf_n_norm.shape
|
|
318
|
+
nmax = (nvals-1)//2
|
|
319
|
+
narr = np.arange(-nmax,nmax+1, dtype=np.int)
|
|
320
|
+
nextM = np.zeros((nvals,nvals))
|
|
321
|
+
for ind, ell in enumerate(narr):
|
|
322
|
+
lminusn = ell-narr
|
|
323
|
+
sel = np.logical_and(lminusn+nmax>=0, lminusn+nmax<nvals)
|
|
324
|
+
nextM[ind,sel] = threepcf_n_norm[(lminusn+nmax)[sel],thet1,thet2].real / threepcf_n_norm[nmax,thet1,thet2].real
|
|
325
|
+
return nextM
|
|
326
|
+
|
|
327
|
+
nvals, nzcombis, ntheta, _ = self.npcf_multipoles_norm.shape
|
|
328
|
+
nmax = nvals-1
|
|
329
|
+
threepcf_n_full = np.zeros((4,2*nmax+1, nzcombis, ntheta, ntheta), dtype=complex)
|
|
330
|
+
threepcf_n_norm_full = np.zeros((2*nmax+1, nzcombis, ntheta, ntheta), dtype=complex)
|
|
331
|
+
threepcf_n_corr = np.zeros(threepcf_n_full.shape, dtype=np.complex)
|
|
332
|
+
threepcf_n_full[:,nmax:] = self.npcf_multipoles
|
|
333
|
+
threepcf_n_norm_full[nmax:] = self.npcf_multipoles_norm
|
|
334
|
+
for nextn in range(1,nvals):
|
|
335
|
+
threepcf_n_full[0,nmax-nextn] = self.npcf_multipoles[0,nextn].transpose(0,2,1)
|
|
336
|
+
threepcf_n_full[1,nmax-nextn] = self.npcf_multipoles[1,nextn].transpose(0,2,1)
|
|
337
|
+
threepcf_n_full[2,nmax-nextn] = self.npcf_multipoles[3,nextn].transpose(0,2,1)
|
|
338
|
+
threepcf_n_full[3,nmax-nextn] = self.npcf_multipoles[2,nextn].transpose(0,2,1)
|
|
339
|
+
threepcf_n_norm_full[nmax-nextn] = self.npcf_multipoles_norm[nextn].transpose(0,2,1)
|
|
340
|
+
|
|
341
|
+
if ret_matrices:
|
|
342
|
+
mats = np.zeros((nzcombis,ntheta,ntheta,nvals,nvals))
|
|
343
|
+
for indz in range(nzcombis):
|
|
344
|
+
#sys.stdout.write("%i"%indz)
|
|
345
|
+
for thet1 in range(ntheta):
|
|
346
|
+
for thet2 in range(ntheta):
|
|
347
|
+
nextM = gen_M_matrix(thet1,thet2,threepcf_n_norm_full[:,indz])
|
|
348
|
+
nextM_inv = np.linalg.inv(nextM)
|
|
349
|
+
if ret_matrices:
|
|
350
|
+
mats[indz,thet1,thet2] = nextM
|
|
351
|
+
for i in range(4):
|
|
352
|
+
threepcf_n_corr[i,:,indz,thet1,thet2] = np.matmul(nextM_inv,threepcf_n_full[i,:,indz,thet1,thet2])
|
|
353
|
+
|
|
354
|
+
self.npcf_multipoles = threepcf_n_corr[:,nmax:]
|
|
355
|
+
self.is_edge_corrected = True
|
|
356
|
+
|
|
357
|
+
if ret_matrices:
|
|
358
|
+
return threepcf_n_corr[:,nmax:], mats
|
|
359
|
+
|
|
360
|
+
# Legacy transform in pure python -- now upgraded to .c
|
|
361
|
+
def _multipoles2npcf_py(self):
|
|
362
|
+
|
|
363
|
+
_, nzcombis, rbins, rbins = np.shape(self.npcf_multipoles[0])
|
|
364
|
+
self.npcf = np.zeros((4, nzcombis, rbins, rbins, len(self.phi)), dtype=complex)
|
|
365
|
+
self.npcf_norm = np.zeros((nzcombis, rbins, rbins, len(self.phi)), dtype=complex)
|
|
366
|
+
ztiler = np.arange(self.nbinsz*self.nbinsz*self.nbinsz).reshape(
|
|
367
|
+
(self.nbinsz,self.nbinsz,self.nbinsz)).transpose(0,2,1).flatten().astype(np.int32)
|
|
368
|
+
|
|
369
|
+
# 3PCF components
|
|
370
|
+
conjmap = [0,1,3,2]
|
|
371
|
+
for elm in range(4):
|
|
372
|
+
for elphi, phi in enumerate(self.phi):
|
|
373
|
+
N0 = 1./(2*np.pi) * self.npcf_multipoles_norm[0].astype(complex)
|
|
374
|
+
tmp = 1./(2*np.pi) * self.npcf_multipoles[elm,0].astype(complex)
|
|
375
|
+
for n in range(1,self.nmax+1):
|
|
376
|
+
_const = 1./(2*np.pi) * np.exp(1J*n*phi)
|
|
377
|
+
tmp += _const * self.npcf_multipoles[elm,n].astype(complex)
|
|
378
|
+
tmp += _const.conj() * self.npcf_multipoles[conjmap[elm],n][ztiler].astype(complex).transpose(0,2,1)
|
|
379
|
+
self.npcf[elm,...,elphi] = tmp
|
|
380
|
+
# Number of triangles
|
|
381
|
+
for elphi, phi in enumerate(self.phi):
|
|
382
|
+
tmptotnorm = 1./(2*np.pi) * self.npcf_multipoles_norm[0].astype(complex)
|
|
383
|
+
for n in range(1,self.nmax+1):
|
|
384
|
+
_const = 1./(2*np.pi) * np.exp(1J*n*phi)
|
|
385
|
+
tmptotnorm += _const * self.npcf_multipoles_norm[n].astype(complex)
|
|
386
|
+
tmptotnorm += _const.conj() * self.npcf_multipoles_norm[n][ztiler].astype(complex).transpose(0,2,1)
|
|
387
|
+
self.npcf_norm[...,elphi] = tmptotnorm
|
|
388
|
+
|
|
389
|
+
if self.is_edge_corrected:
|
|
390
|
+
dphi = self.phi[1] - self.phi[0]
|
|
391
|
+
N0 = dphi/(2*np.pi) * self.npcf_multipoles_norm[self.nmax].astype(complex)
|
|
392
|
+
sel_zero = np.isnan(N0)
|
|
393
|
+
_a = self.npcf
|
|
394
|
+
_b = N0.real[np.newaxis, :, :, :, np.newaxis]
|
|
395
|
+
self.npcf = np.divide(_a, _b, out=np.zeros_like(_a), where=_b>0)
|
|
396
|
+
else:
|
|
397
|
+
_a = self.npcf
|
|
398
|
+
_b = self.npcf_norm
|
|
399
|
+
self.npcf = np.divide(_a, _b, out=np.zeros_like(_a), where=_b>0)
|
|
400
|
+
self.projection = "X"
|
|
401
|
+
|
|
402
|
+
def multipoles2npcf(self, projection='Centroid'):
|
|
403
|
+
r"""
|
|
404
|
+
Notes
|
|
405
|
+
-----
|
|
406
|
+
The Upsilon and Norms are only computed for the n>0 multipoles. The n<0 multipoles are recovered by symmetry considerations given in Eq A.6 in Porth+23.
|
|
407
|
+
"""
|
|
408
|
+
assert(projection in self.projections_avail)
|
|
409
|
+
int_projection = {'X':0,'Centroid':1}
|
|
410
|
+
_, nzcombis, rbins, rbins = np.shape(self.npcf_multipoles[0])
|
|
411
|
+
thisnpcf = np.zeros(4*self.nbinsz*self.nbinsz*self.nbinsz*self.nbinsr*self.nbinsr*len(self.phi), dtype=np.complex128)
|
|
412
|
+
thisnpcf_norm = np.zeros(self.nbinsz*self.nbinsz*self.nbinsz*self.nbinsr*self.nbinsr*len(self.phi), dtype=np.complex128)
|
|
413
|
+
self.clib.multipoles2npcf_ggg(
|
|
414
|
+
self.npcf_multipoles.flatten(), self.npcf_multipoles_norm.flatten(), np.int32(self.nmax), np.int32(self.nbinsz),
|
|
415
|
+
self.bin_centers_mean, np.int32(self.nbinsr), self.phi.astype(np.float64), np.int32(self.nbinsphi[0]),
|
|
416
|
+
np.int32(int_projection[projection]), np.int32(self.nthreads), thisnpcf, thisnpcf_norm)
|
|
417
|
+
self.npcf = thisnpcf.reshape((4,nzcombis,self.nbinsr,self.nbinsr,len(self.phi)))
|
|
418
|
+
self.npcf_norm = thisnpcf_norm.reshape((nzcombis,self.nbinsr,self.nbinsr,len(self.phi)))
|
|
419
|
+
self.projection = projection
|
|
420
|
+
|
|
421
|
+
## PROJECTIONS (Preferably use direct in c-level) ##
|
|
422
|
+
def projectnpcf(self, projection):
|
|
423
|
+
super()._projectnpcf(self, projection)
|
|
424
|
+
|
|
425
|
+
def _x2centroid(self):
|
|
426
|
+
gammas_cen = np.zeros_like(self.npcf)
|
|
427
|
+
pimod = lambda x: x%(2*np.pi) - 2*np.pi*(x%(2*np.pi)>=np.pi)
|
|
428
|
+
npcf_cen = np.zeros(self.npcf.shape, dtype=complex)
|
|
429
|
+
_centers = np.mean(self.bin_centers, axis=0)
|
|
430
|
+
for elb1, bin1 in enumerate(_centers):
|
|
431
|
+
for elb2, bin2 in enumerate(_centers):
|
|
432
|
+
bin3 = np.sqrt(bin1**2 + bin2**2 - 2*bin1*bin2*np.cos(self.phi))
|
|
433
|
+
phiexp = np.exp(1J*self.phi)
|
|
434
|
+
phiexp_c = np.exp(-1J*self.phi)
|
|
435
|
+
prod1 = (bin1 + bin2*phiexp_c)/(bin1 + bin2*phiexp) #q1
|
|
436
|
+
prod2 = (2*bin1 - bin2*phiexp_c)/(2*bin1 - bin2*phiexp) #q2
|
|
437
|
+
prod3 = (2*bin2*phiexp_c - bin1)/(2*bin2*phiexp - bin1) #q3
|
|
438
|
+
prod1_inv = prod1.conj()/np.abs(prod1)
|
|
439
|
+
prod2_inv = prod2.conj()/np.abs(prod2)
|
|
440
|
+
prod3_inv = prod3.conj()/np.abs(prod3)
|
|
441
|
+
rot_nom = np.zeros((4,len(self.phi)))
|
|
442
|
+
rot_nom[0] = pimod(np.angle(prod1*prod2*prod3*np.exp(3*1J*self.phi)))
|
|
443
|
+
rot_nom[1] = pimod(np.angle(prod1_inv*prod2*prod3*np.exp(1J*self.phi)))
|
|
444
|
+
rot_nom[2] = pimod(np.angle(prod1*prod2_inv*prod3*np.exp(3*1J*self.phi)))
|
|
445
|
+
rot_nom[3] = pimod(np.angle(prod1*prod2*prod3_inv*np.exp(-1J*self.phi)))
|
|
446
|
+
gammas_cen[:,:,elb1,elb2] = self.npcf[:,:,elb1,elb2]*np.exp(1j*rot_nom)[:,np.newaxis,:]
|
|
447
|
+
return gammas_cen
|
|
448
|
+
|
|
449
|
+
def computeMap3(self, radii, do_multiscale=False, tofile=False, filtercache=None):
|
|
450
|
+
"""
|
|
451
|
+
Compute third-order aperture statistics using the polynomial filter.
|
|
452
|
+
"""
|
|
453
|
+
|
|
454
|
+
if self.npcf is None and self.npcf_multipoles is not None:
|
|
455
|
+
self.multipoles2npcf(projection='Centroid')
|
|
456
|
+
|
|
457
|
+
if self.projection != "Centroid":
|
|
458
|
+
self.projectnpcf("Centroid")
|
|
459
|
+
|
|
460
|
+
nradii = len(radii)
|
|
461
|
+
if not do_multiscale:
|
|
462
|
+
nrcombis = nradii
|
|
463
|
+
filterfunc = self._map3_filtergrid_singleR
|
|
464
|
+
_rcut = 1
|
|
465
|
+
else:
|
|
466
|
+
nrcombis = nradii*nradii*nradii
|
|
467
|
+
filterfunc = self._map3_filtergrid_multiR
|
|
468
|
+
_rcut = nradii
|
|
469
|
+
map3s = np.zeros((8, self.nzcombis, nrcombis), dtype=complex)
|
|
470
|
+
M3 = np.zeros((self.nzcombis, nrcombis), dtype=complex)
|
|
471
|
+
M2M1 = np.zeros((self.nzcombis, nrcombis), dtype=complex)
|
|
472
|
+
M2M2 = np.zeros((self.nzcombis, nrcombis), dtype=complex)
|
|
473
|
+
M2M3 = np.zeros((self.nzcombis, nrcombis), dtype=complex)
|
|
474
|
+
tmprcombi = 0
|
|
475
|
+
|
|
476
|
+
for elr1, R1 in enumerate(radii):
|
|
477
|
+
for elr2, R2 in enumerate(radii[:_rcut]):
|
|
478
|
+
for elr3, R3 in enumerate(radii[:_rcut]):
|
|
479
|
+
if not do_multiscale:
|
|
480
|
+
R2 = R1
|
|
481
|
+
R3 = R1
|
|
482
|
+
if filtercache is not None:
|
|
483
|
+
T0, T3_123, T3_231, T3_312 = filtercache[tmprcombi][0], filtercache[tmprcombi][1], filtercache[tmprcombi][2], filtercache[tmprcombi][3]
|
|
484
|
+
else:
|
|
485
|
+
T0, T3_123, T3_231, T3_312 = filterfunc(R1, R2, R3)
|
|
486
|
+
M3[:,tmprcombi] = np.nansum(T0*self.npcf[0,...],axis=(1,2,3))
|
|
487
|
+
M2M1[:,tmprcombi] = np.nansum(T3_123*self.npcf[1,...],axis=(1,2,3))
|
|
488
|
+
M2M2[:,tmprcombi] = np.nansum(T3_231*self.npcf[2,...],axis=(1,2,3))
|
|
489
|
+
M2M3[:,tmprcombi] = np.nansum(T3_312*self.npcf[3,...],axis=(1,2,3))
|
|
490
|
+
tmprcombi += 1
|
|
491
|
+
map3s[0] = 1./4. * (+M2M1+M2M2+M2M3 + M3).real # MapMapMap
|
|
492
|
+
map3s[1] = 1./4. * (+M2M1+M2M2-M2M3 + M3).imag # MapMapMx
|
|
493
|
+
map3s[2] = 1./4. * (+M2M1-M2M2+M2M3 + M3).imag # MapMxMap
|
|
494
|
+
map3s[3] = 1./4. * (-M2M1+M2M2+M2M3 + M3).imag # MxMapMap
|
|
495
|
+
map3s[4] = 1./4. * (-M2M1+M2M2+M2M3 - M3).real # MapMxMx
|
|
496
|
+
map3s[5] = 1./4. * (+M2M1-M2M2+M2M3 - M3).real # MxMapMx
|
|
497
|
+
map3s[6] = 1./4. * (+M2M1+M2M2-M2M3 - M3).real # MxMxMap
|
|
498
|
+
map3s[7] = 1./4. * (+M2M1+M2M2+M2M3 - M3).imag # MxMxMx
|
|
499
|
+
|
|
500
|
+
if tofile:
|
|
501
|
+
# Write to file
|
|
502
|
+
pass
|
|
503
|
+
|
|
504
|
+
return map3s
|
|
505
|
+
|
|
506
|
+
def _map3_filtergrid_singleR(self, R1, R2, R3):
|
|
507
|
+
return self.__map3_filtergrid_singleR(R1, R2, R3, self.bin_edges, self.bin_centers_mean, self.phi)
|
|
508
|
+
|
|
509
|
+
@staticmethod
|
|
510
|
+
@jit(nopython=True)
|
|
511
|
+
def __map3_filtergrid_singleR(R1, R2, R3, normys_edges, normys_centers, phis):
|
|
512
|
+
|
|
513
|
+
# To avoid zero divisions we set some default bin centers for the evaluation of the filter
|
|
514
|
+
# As for those positions the 3pcf is zero those will not contribute to the map3 integral
|
|
515
|
+
if (np.min(normys_centers)==0):
|
|
516
|
+
_sel = normys_centers!=0
|
|
517
|
+
_avratios = np.mean(normys_centers[_sel]/normys_edges[_sel])
|
|
518
|
+
normys_centers[~_sel] = _avratios*normys_edges[~_sel]
|
|
519
|
+
|
|
520
|
+
R_ap = R1
|
|
521
|
+
nbinsr = len(normys_centers)
|
|
522
|
+
nbinsphi = len(phis)
|
|
523
|
+
_cphis = np.cos(phis)
|
|
524
|
+
_c2phis = np.cos(2*phis)
|
|
525
|
+
_sphis = np.sin(phis)
|
|
526
|
+
_ephis = np.e**(1J*phis)
|
|
527
|
+
_ephisc = np.e**(-1J*phis)
|
|
528
|
+
_e2phis = np.e**(2J*phis)
|
|
529
|
+
_e2phisc = np.e**(-2J*phis)
|
|
530
|
+
T0 = np.zeros((nbinsr, nbinsr, nbinsphi), dtype=nb_complex128)
|
|
531
|
+
T3_123 = np.zeros((nbinsr, nbinsr, nbinsphi), dtype=nb_complex128)
|
|
532
|
+
T3_231 = np.zeros((nbinsr, nbinsr, nbinsphi), dtype=nb_complex128)
|
|
533
|
+
T3_312 = np.zeros((nbinsr, nbinsr, nbinsphi), dtype=nb_complex128)
|
|
534
|
+
for elb1 in range(nbinsr):
|
|
535
|
+
_y1 = normys_centers[elb1]
|
|
536
|
+
_dbin1 = normys_edges[elb1+1] - normys_edges[elb1]
|
|
537
|
+
for elb2 in range(nbinsr):
|
|
538
|
+
_y2 = normys_centers[elb2]
|
|
539
|
+
_y14 = _y1**4
|
|
540
|
+
_y13y2 = _y1**3*_y2
|
|
541
|
+
_y12y22 = _y1**2*_y2**2
|
|
542
|
+
_y1y23 = _y1*_y2**3
|
|
543
|
+
_y24 = _y2**4
|
|
544
|
+
_dbin2 = normys_edges[elb2+1] - normys_edges[elb2]
|
|
545
|
+
_dbinphi = phis[1] - phis[0]
|
|
546
|
+
_absq1s = 1./9.*(4*_y1**2 - 4*_y1*_y2*_cphis + 1*_y2**2)
|
|
547
|
+
_absq2s = 1./9.*(1*_y1**2 - 4*_y1*_y2*_cphis + 4*_y2**2)
|
|
548
|
+
_absq3s = 1./9.*(1*_y1**2 + 2*_y1*_y2*_cphis + 1*_y2**2)
|
|
549
|
+
_absq123s = 2./3. * (_y1**2+_y2**2-_y1*_y2*_cphis)
|
|
550
|
+
_absq1q2q3_2 = _absq1s*_absq2s*_absq3s
|
|
551
|
+
_measures = _y1*_dbin1/R_ap**2 * _y2*_dbin2/R_ap**2 * _dbinphi/(2*np.pi)
|
|
552
|
+
nextT0 = _absq1q2q3_2/R_ap**6 * np.e**(-_absq123s/(2*R_ap**2))
|
|
553
|
+
T0[elb1,elb2] = 1./24. * _measures * nextT0
|
|
554
|
+
_tmp1 = _y1**4 + _y2**4 + _y1**2*_y2**2 * (2*np.cos(2*phis)-5.)
|
|
555
|
+
_tmp2 = (_y1**2+_y2**2)*_cphis + 9J*(_y1**2-_y2**2)*_sphis
|
|
556
|
+
q1q2q3starsq = -1./81*(2*_tmp1 - _y1*_y2*_tmp2)
|
|
557
|
+
nextT3_123 = np.e**(-_absq123s/(2*R_ap**2)) * (1./24*_absq1q2q3_2/R_ap**6 -
|
|
558
|
+
1./9.*q1q2q3starsq/R_ap**4 +
|
|
559
|
+
1./27*(q1q2q3starsq**2/(_absq1q2q3_2*R_ap**2) +
|
|
560
|
+
2*q1q2q3starsq/(_absq3s*R_ap**2)))
|
|
561
|
+
_231inner = -4*_y14 + 2*_y24 + _y13y2*8*_cphis + _y12y22*(8*_e2phis-4-_e2phisc) + _y1y23*(_ephisc-8*_ephis)
|
|
562
|
+
q2q3q1starsq = -1./81*(_231inner)
|
|
563
|
+
nextT3_231 = np.e**(-_absq123s/(2*R_ap**2)) * (1./24*_absq1q2q3_2/R_ap**6 -
|
|
564
|
+
1./9.*q2q3q1starsq/R_ap**4 +
|
|
565
|
+
1./27*(q2q3q1starsq**2/(_absq1q2q3_2*R_ap**2) +
|
|
566
|
+
2*q2q3q1starsq/(_absq1s*R_ap**2)))
|
|
567
|
+
_312inner = 2*_y14 - 4*_y24 - _y13y2*(8*_ephisc-_ephis) - _y12y22*(4+_e2phis-8*_e2phisc) + 8*_y1y23*_cphis
|
|
568
|
+
q3q1q2starsq = -1./81*(_312inner)
|
|
569
|
+
nextT3_312 = np.e**(-_absq123s/(2*R_ap**2)) * (1./24*_absq1q2q3_2/R_ap**6 -
|
|
570
|
+
1./9.*q3q1q2starsq/R_ap**4 +
|
|
571
|
+
1./27*(q3q1q2starsq**2/(_absq1q2q3_2*R_ap**2) +
|
|
572
|
+
2*q3q1q2starsq/(_absq2s*R_ap**2)))
|
|
573
|
+
T3_123[elb1,elb2] = _measures * nextT3_123
|
|
574
|
+
T3_231[elb1,elb2] = _measures * nextT3_231
|
|
575
|
+
T3_312[elb1,elb2] = _measures * nextT3_312
|
|
576
|
+
|
|
577
|
+
return T0, T3_123, T3_231, T3_312
|
|
578
|
+
|
|
579
|
+
def _map3_filtergrid_multiR(self, R1, R2, R3):
|
|
580
|
+
return self.__map3_filtergrid_multiR(R1, R2, R3, self.bin_edges, self.bin_centers_mean, self.phi, include_measure=True)
|
|
581
|
+
|
|
582
|
+
@staticmethod
|
|
583
|
+
@jit(nopython=True)
|
|
584
|
+
def __map3_filtergrid_multiR(R1, R2, R3, normys_edges, normys_centers, phis, include_measure=True):
|
|
585
|
+
|
|
586
|
+
# To avoid zero divisions we set some default bin centers for the evaluation of the filter
|
|
587
|
+
# As for those positions the 3pcf is zero those will not contribute to the map3 integral
|
|
588
|
+
if (np.min(normys_centers)==0):
|
|
589
|
+
_sel = normys_centers!=0
|
|
590
|
+
_avratios = np.mean(normys_centers[_sel]/normys_edges[_sel])
|
|
591
|
+
normys_centers[~_sel] = _avratios*normys_edges[~_sel]
|
|
592
|
+
|
|
593
|
+
nbinsr = len(normys_centers)
|
|
594
|
+
nbinsphi = len(phis)
|
|
595
|
+
_cphis = np.cos(phis)
|
|
596
|
+
_c2phis = np.cos(2*phis)
|
|
597
|
+
_sphis = np.sin(phis)
|
|
598
|
+
_ephis = np.e**(1J*phis)
|
|
599
|
+
_ephisc = np.e**(-1J*phis)
|
|
600
|
+
_e2phis = np.e**(2J*phis)
|
|
601
|
+
_e2phisc = np.e**(-2J*phis)
|
|
602
|
+
T0 = np.zeros((nbinsr, nbinsr, nbinsphi), dtype=nb_complex128)
|
|
603
|
+
T3_123 = np.zeros((nbinsr, nbinsr, nbinsphi), dtype=nb_complex128)
|
|
604
|
+
T3_231 = np.zeros((nbinsr, nbinsr, nbinsphi), dtype=nb_complex128)
|
|
605
|
+
T3_312 = np.zeros((nbinsr, nbinsr, nbinsphi), dtype=nb_complex128)
|
|
606
|
+
for elb1 in range(nbinsr):
|
|
607
|
+
_y1 = normys_centers[elb1]
|
|
608
|
+
_dbin1 = normys_edges[elb1+1] - normys_edges[elb1]
|
|
609
|
+
for elb2 in range(nbinsr):
|
|
610
|
+
Theta2 = np.sqrt((R1**2*R2**2 + R1**2*R3**2 + R2**2*R3**2)/3)
|
|
611
|
+
S = R1**2*R2**2*R3**2/Theta2**3
|
|
612
|
+
|
|
613
|
+
_y2 = normys_centers[elb2]
|
|
614
|
+
_y14 = _y1**4
|
|
615
|
+
_y13y2 = _y1**3*_y2
|
|
616
|
+
_y12y22 = _y1**2*_y2**2
|
|
617
|
+
_y1y23 = _y1*_y2**3
|
|
618
|
+
_y24 = _y2**4
|
|
619
|
+
_dbin2 = normys_edges[elb2+1] - normys_edges[elb2]
|
|
620
|
+
_dbinphi = phis[1] - phis[0]
|
|
621
|
+
_absq1s = 1./9.*(4*_y1**2 - 4*_y1*_y2*_cphis + 1*_y2**2)
|
|
622
|
+
_absq2s = 1./9.*(1*_y1**2 - 4*_y1*_y2*_cphis + 4*_y2**2)
|
|
623
|
+
_absq3s = 1./9.*(1*_y1**2 + 2*_y1*_y2*_cphis + 1*_y2**2)
|
|
624
|
+
_absq123s = 2./3. * (_y1**2+_y2**2-_y1*_y2*_cphis)
|
|
625
|
+
_absq1q2q3_2 = _absq1s*_absq2s*_absq3s
|
|
626
|
+
|
|
627
|
+
Z = ((-R1**2+2*R2**2+2*R3**2)*_absq1s + (2*R1**2-R2**2+2*R3**2)*_absq2s + (2*R1**2+2*R2**2-R3**2)*_absq3s)/(6*Theta2**2)
|
|
628
|
+
_frac231c = 1./3.*_y2*(2*_y1*_ephis-_y2)/_absq1s
|
|
629
|
+
_frac312c = 1./3.*_y1*(_y1-2*_y2*_ephisc)/_absq2s
|
|
630
|
+
_frac123c = 1./3.*(_y2**2-_y1**2+2J*_y1*_y2*_sphis)/_absq3s
|
|
631
|
+
f1 = (R2**2+R3**2)/(2*Theta2) + _frac231c * (R2**2-R3**2)/(6*Theta2)
|
|
632
|
+
f2 = (R1**2+R3**2)/(2*Theta2) + _frac312c * (R3**2-R1**2)/(6*Theta2)
|
|
633
|
+
f3 = (R1**2+R2**2)/(2*Theta2) + _frac123c * (R1**2-R2**2)/(6*Theta2)
|
|
634
|
+
f1c = f1.conj()
|
|
635
|
+
f2c = f2.conj()
|
|
636
|
+
f3c = f3.conj()
|
|
637
|
+
g1c = (R2**2*R3**2/Theta2**2 + R1**2*(R3**2-R2**2)/(3*Theta2**2)*_frac231c).conj()
|
|
638
|
+
g2c = (R3**2*R1**2/Theta2**2 + R2**2*(R1**2-R3**2)/(3*Theta2**2)*_frac312c).conj()
|
|
639
|
+
g3c = (R1**2*R2**2/Theta2**2 + R3**2*(R2**2-R1**2)/(3*Theta2**2)*_frac123c).conj()
|
|
640
|
+
_measures = _y1*_dbin1/Theta2 * _y2*_dbin2/Theta2 * _dbinphi/(2*np.pi)
|
|
641
|
+
if not include_measure:
|
|
642
|
+
_measures/=_measures
|
|
643
|
+
nextT0 = _absq1q2q3_2/Theta2**3 * f1c**2*f2c**2*f3c**2 * np.e**(-Z)
|
|
644
|
+
T0[elb1,elb2] = S/24. * _measures * nextT0
|
|
645
|
+
|
|
646
|
+
_tmp1 = _y1**4 + _y2**4 + _y1**2*_y2**2 * (2*np.cos(2*phis)-5.)
|
|
647
|
+
_tmp2 = (_y1**2+_y2**2)*_cphis + 9J*(_y1**2-_y2**2)*_sphis
|
|
648
|
+
q1q2q3starsq = -1./81*(2*_tmp1 - _y1*_y2*_tmp2)
|
|
649
|
+
nextT3_123 = np.e**(-Z) * (1./24*_absq1q2q3_2/Theta2**3 * f1c**2*f2c**2*f3**2 -
|
|
650
|
+
1./9.*q1q2q3starsq/Theta2**2 * f1c*f2c*f3*g3c +
|
|
651
|
+
1./27*(q1q2q3starsq**2/(_absq1q2q3_2*Theta2) * g3c**2 +
|
|
652
|
+
2*R1**2*R2**2/Theta2**2 * q1q2q3starsq/(_absq3s*Theta2) * f1c*f2c))
|
|
653
|
+
_231inner = -4*_y14 + 2*_y24 + _y13y2*8*_cphis + _y12y22*(8*_e2phis-4-_e2phisc) + _y1y23*(_ephisc-8*_ephis)
|
|
654
|
+
q2q3q1starsq = -1./81*(_231inner)
|
|
655
|
+
nextT3_231 = np.e**(-Z) * (1./24*_absq1q2q3_2/Theta2**3 * f2c**2*f3c**2*f1**2 -
|
|
656
|
+
1./9.*q2q3q1starsq/Theta2**2 * f2c*f3c*f1*g1c +
|
|
657
|
+
1./27*(q2q3q1starsq**2/(_absq1q2q3_2*Theta2) * g1c**2 +
|
|
658
|
+
2*R2**2*R3**2/Theta2**2 * q2q3q1starsq/(_absq1s*Theta2) * f2c*f3c))
|
|
659
|
+
_312inner = 2*_y14 - 4*_y24 - _y13y2*(8*_ephisc-_ephis) - _y12y22*(4+_e2phis-8*_e2phisc) + 8*_y1y23*_cphis
|
|
660
|
+
q3q1q2starsq = -1./81*(_312inner)
|
|
661
|
+
nextT3_312 = np.e**(-Z) * (1./24*_absq1q2q3_2/Theta2**3 * f3c**2*f1c**2*f2**2 -
|
|
662
|
+
1./9.*q3q1q2starsq/Theta2**2 * f3c*f1c*f2*g2c +
|
|
663
|
+
1./27*(q3q1q2starsq**2/(_absq1q2q3_2*Theta2) * g2c**2 +
|
|
664
|
+
2*R3**2*R1**2/Theta2**2 * q3q1q2starsq/(_absq2s*Theta2) * f3c*f1c))
|
|
665
|
+
|
|
666
|
+
T3_123[elb1,elb2] = S * _measures * nextT3_123
|
|
667
|
+
T3_231[elb1,elb2] = S * _measures * nextT3_231
|
|
668
|
+
T3_312[elb1,elb2] = S * _measures * nextT3_312
|
|
669
|
+
|
|
670
|
+
return T0, T3_123, T3_231, T3_312
|
|
671
|
+
|
|
672
|
+
|
|
673
|
+
class GNNCorrelation(BinnedNPCF):
|
|
674
|
+
r""" Class containing methods to measure and and obtain statistics that are built
|
|
675
|
+
from third-order source-lens-lens (G3L) correlation functions.
|
|
676
|
+
|
|
677
|
+
Attributes
|
|
678
|
+
----------
|
|
679
|
+
min_sep: float
|
|
680
|
+
The smallest distance of each vertex for which the NPCF is computed.
|
|
681
|
+
max_sep: float
|
|
682
|
+
The largest distance of each vertex for which the NPCF is computed.
|
|
683
|
+
zweighting: bool
|
|
684
|
+
Has no effect at the moment
|
|
685
|
+
zweighting_sigma: bool
|
|
686
|
+
Has not effect at the moment
|
|
687
|
+
|
|
688
|
+
Notes
|
|
689
|
+
-----
|
|
690
|
+
Inherits all other parameters and attributes from :class:`BinnedNPCF`.
|
|
691
|
+
Additional child-specific parameters can be passed via ``kwargs``.
|
|
692
|
+
Either ``nbinsr`` or ``binsize`` has to be provided to fix the binning scheme .
|
|
693
|
+
"""
|
|
694
|
+
|
|
695
|
+
def __init__(self, min_sep, max_sep, zweighting=False, zweighting_sigma=None, **kwargs):
|
|
696
|
+
super().__init__(3, [2,0,0], n_cfs=1, min_sep=min_sep, max_sep=max_sep, **kwargs)
|
|
697
|
+
self.nmax = self.nmaxs[0]
|
|
698
|
+
self.phi = self.phis[0]
|
|
699
|
+
self.projection = None
|
|
700
|
+
self.projections_avail = [None, "X"]
|
|
701
|
+
self.nbinsz_source = None
|
|
702
|
+
self.nbinsz_lens = None
|
|
703
|
+
|
|
704
|
+
assert(zweighting in [True, False])
|
|
705
|
+
self.zweighting = zweighting
|
|
706
|
+
self.zweighting_sigma = zweighting_sigma
|
|
707
|
+
if not self.zweighting :
|
|
708
|
+
self.zweighting_sigma = None
|
|
709
|
+
else:
|
|
710
|
+
assert(isinstance(self.zweighting_sigma, float))
|
|
711
|
+
|
|
712
|
+
# (Add here any newly implemented projections)
|
|
713
|
+
self._initprojections(self)
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
def __process_patches(self, cat_source, cat_lens, dotomo_source=True, dotomo_lens=True, rotsignflip=False,
|
|
717
|
+
apply_edge_correction=False, save_patchres=False, save_filebase="", keep_patchres=False):
|
|
718
|
+
if save_patchres:
|
|
719
|
+
if not Path(save_patchres).is_dir():
|
|
720
|
+
raise ValueError('Path to directory does not exist.')
|
|
721
|
+
|
|
722
|
+
for elp in range(cat_source.npatches):
|
|
723
|
+
if self._verbose_python:
|
|
724
|
+
print('Doing patch %i/%i'%(elp+1,cat_source.npatches))
|
|
725
|
+
# Compute statistics on patch
|
|
726
|
+
pscat = cat_source.frompatchind(elp,rotsignflip=rotsignflip)
|
|
727
|
+
plcat = cat_lens.frompatchind(elp)
|
|
728
|
+
pcorr = GNNCorrelation(
|
|
729
|
+
min_sep=self.min_sep,
|
|
730
|
+
max_sep=self.max_sep,
|
|
731
|
+
nbinsr=self.nbinsr,
|
|
732
|
+
nbinsphi=self.nbinsphi,
|
|
733
|
+
nmaxs=self.nmaxs,
|
|
734
|
+
method=self.method,
|
|
735
|
+
multicountcorr=self.multicountcorr,
|
|
736
|
+
shuffle_pix=self.shuffle_pix,
|
|
737
|
+
tree_resos=self.tree_resos,
|
|
738
|
+
rmin_pixsize=self.rmin_pixsize,
|
|
739
|
+
resoshift_leafs=self.resoshift_leafs,
|
|
740
|
+
minresoind_leaf=self.minresoind_leaf,
|
|
741
|
+
maxresoind_leaf=self.maxresoind_leaf,
|
|
742
|
+
nthreads=self.nthreads,
|
|
743
|
+
verbosity=self.verbosity)
|
|
744
|
+
pcorr.process(pscat, plcat, dotomo_source=dotomo_source, dotomo_lens=dotomo_lens)
|
|
745
|
+
|
|
746
|
+
# Update the total measurement
|
|
747
|
+
if elp == 0:
|
|
748
|
+
self.nbinsz_source = pcorr.nbinsz_source
|
|
749
|
+
self.nbinsz_lens = pcorr.nbinsz_lens
|
|
750
|
+
self.bin_centers = np.zeros_like(pcorr.bin_centers)
|
|
751
|
+
self.npcf_multipoles = np.zeros_like(pcorr.npcf_multipoles)
|
|
752
|
+
self.npcf_multipoles_norm = np.zeros_like(pcorr.npcf_multipoles_norm)
|
|
753
|
+
_footnorm = np.zeros_like(pcorr.bin_centers)
|
|
754
|
+
if keep_patchres:
|
|
755
|
+
centers_patches = np.zeros((cat_source.npatches, *pcorr.bin_centers.shape), dtype=pcorr.bin_centers.dtype)
|
|
756
|
+
npcf_multipoles_patches = np.zeros((cat_source.npatches, *pcorr.npcf_multipoles.shape), dtype=pcorr.npcf_multipoles.dtype)
|
|
757
|
+
npcf_multipoles_norm_patches = np.zeros((cat_source.npatches, *pcorr.npcf_multipoles_norm.shape), dtype=pcorr.npcf_multipoles_norm.dtype)
|
|
758
|
+
_shelltriplets = np.array([[[pcorr.npcf_multipoles_norm[0,zs*self.nbinsz_lens*self.nbinsz_lens+zl*self.nbinsz_lens+zl,i,i].real
|
|
759
|
+
for i in range(pcorr.nbinsr)] for zl in range(self.nbinsz_lens)] for zs in range(self.nbinsz_source)])
|
|
760
|
+
# Rough estimate of scaling of pair counts based on zeroth multipole of triplets. Note that we might get nans here due to numerical
|
|
761
|
+
# inaccuracies in the multiple counting corrections for bins with zero triplets, so we force those values to be zero.
|
|
762
|
+
_patchnorm = np.nan_to_num(np.sqrt(_shelltriplets))
|
|
763
|
+
self.bin_centers += _patchnorm*pcorr.bin_centers
|
|
764
|
+
_footnorm += _patchnorm
|
|
765
|
+
self.npcf_multipoles += pcorr.npcf_multipoles
|
|
766
|
+
self.npcf_multipoles_norm += pcorr.npcf_multipoles_norm
|
|
767
|
+
if keep_patchres:
|
|
768
|
+
centers_patches[elp] += pcorr.bin_centers
|
|
769
|
+
npcf_multipoles_patches[elp] += pcorr.npcf_multipoles
|
|
770
|
+
npcf_multipoles_norm_patches[elp] += pcorr.npcf_multipoles_norm
|
|
771
|
+
if save_patchres:
|
|
772
|
+
pcorr.saveinst(save_patchres, save_filebase+'_patch%i'%elp)
|
|
773
|
+
|
|
774
|
+
# Finalize the measurement on the full footprint
|
|
775
|
+
self.bin_centers = np.divide(self.bin_centers,_footnorm, out=np.zeros_like(self.bin_centers), where=_footnorm>0)
|
|
776
|
+
self.bin_centers_mean =np.mean(self.bin_centers, axis=(0,1))
|
|
777
|
+
self.projection = "X"
|
|
778
|
+
|
|
779
|
+
if keep_patchres:
|
|
780
|
+
return centers_patches, npcf_multipoles_patches, npcf_multipoles_norm_patches
|
|
781
|
+
|
|
782
|
+
# TODO: Include z-weighting in estimator
|
|
783
|
+
# * False --> No z-weighting, nothing to do
|
|
784
|
+
# * True --> Tomographic zweighting: Use effective weight for each tomo bin combi. Do computation as tomo case with
|
|
785
|
+
# no z-weighting and then weight in postprocessing where (zs, zl1, zl2) --> w_{zl1, zl2} * (zs)
|
|
786
|
+
# As this could be many zbins, might want to only allow certain zcombis -- i.e. neighbouring zbins.
|
|
787
|
+
# Functional form similar to https://arxiv.org/pdf/1909.06190.pdf
|
|
788
|
+
# * Note that for spectroscopic catalogs we cannot do a full spectroscopic weighting as done i.e. the brute-force method
|
|
789
|
+
# in https://arxiv.org/pdf/1909.06190.pdf, as this breaks the multipole decomposition.
|
|
790
|
+
# * In general, think about what could be a consistent way get a good compromise between speed vs S/N. One extreme would
|
|
791
|
+
# be just to use some broad bins and and the std within them (so 'thinner' bins have more weight). Other extreme would
|
|
792
|
+
# be many small zbins with proper cross-weighting and maximum distance --> Becomes less efficient for more bins.
|
|
793
|
+
def process(self, cat_source, cat_lens, dotomo_source=True, dotomo_lens=True, rotsignflip=False, apply_edge_correction=False,
|
|
794
|
+
save_patchres=False, save_filebase="", keep_patchres=False):
|
|
795
|
+
r"""
|
|
796
|
+
Compute a shear-lens-lens correlation provided a source and a lens catalog.
|
|
797
|
+
|
|
798
|
+
Parameters
|
|
799
|
+
----------
|
|
800
|
+
cat_source: orpheus.SpinTracerCatalog
|
|
801
|
+
The source catalog which is processed
|
|
802
|
+
cat_lens: orpheus.ScalarTracerCatalog
|
|
803
|
+
The lens catalog which is processed
|
|
804
|
+
dotomo_source: bool
|
|
805
|
+
Flag that decides whether the tomographic information in the source catalog should be used. Defaults to `True`.
|
|
806
|
+
dotomo_lens: bool
|
|
807
|
+
Flag that decides whether the tomographic information in the lens catalog should be used. Defaults to `True`.
|
|
808
|
+
rotsignflip: bool
|
|
809
|
+
If the shape catalog is has been decomposed in patches, choose whether the rotation angle should be flipped.
|
|
810
|
+
For simulated data this was always ok to set to 'False`. Defaults to `False`.
|
|
811
|
+
apply_edge_correction: bool
|
|
812
|
+
Flag that decides how the NPCF in the real space basis is computed.
|
|
813
|
+
* If set to `True` the computation is done via edge-correcting the GNN-multipoles
|
|
814
|
+
* If set to `False` both GNN and NNN are transformed separately and the ratio is done in the real-space basis
|
|
815
|
+
Defaults to `False`.
|
|
816
|
+
save_patchres: bool or str
|
|
817
|
+
If the shape catalog is has been decomposed in patches, flag whether to save the GG measurements on the individual patches.
|
|
818
|
+
Note that the path needs to exist, otherwise a `ValueError` is raised. For a flat-sky catalog this parameter
|
|
819
|
+
has no effect. Defaults to `False`
|
|
820
|
+
save_filebase: str
|
|
821
|
+
Base of the filenames in which the patches are saved. The full filename will be `<save_patchres>/<save_filebase>_patchxx.npz`.
|
|
822
|
+
Only has an effect if the shape catalog consists of multiple patches and `save_patchres` is not `False`.
|
|
823
|
+
keep_patchres: bool
|
|
824
|
+
If the catalog consists of multiple patches, returns all measurements on the patches. Defaults to `False`.
|
|
825
|
+
"""
|
|
826
|
+
self._checkcats([cat_source, cat_lens, cat_lens], [2, 0, 0])
|
|
827
|
+
|
|
828
|
+
# Catch typical errors, i.e. incompatible catalogs or missin patch decompositions
|
|
829
|
+
if cat_source.geometry=='spherical' and cat_source.patchinds is None:
|
|
830
|
+
raise ValueError('Error: Spherical catalog needs to be first decomposed into patches using the Catalog._topatches method.')
|
|
831
|
+
if cat_lens.geometry=='spherical' and cat_lens.patchinds is None:
|
|
832
|
+
raise ValueError('Error: Spherical catalog needs to be first decomposed into patches using the Catalog._topatches method.')
|
|
833
|
+
if cat_source.geometry != cat_lens.geometry:
|
|
834
|
+
raise ValueError('Incompatible geometries of source catalog (%s) and lens catalog (%s).'%(
|
|
835
|
+
cat_source.geometry,cat_lens.geometry))
|
|
836
|
+
|
|
837
|
+
# Catalog consist of multiple patches
|
|
838
|
+
if (cat_source.patchinds is not None) and (cat_lens.patchinds is not None):
|
|
839
|
+
return self.__process_patches(cat_source, cat_lens, dotomo_source=dotomo_source, dotomo_lens=dotomo_lens,
|
|
840
|
+
rotsignflip=rotsignflip, apply_edge_correction=apply_edge_correction,
|
|
841
|
+
save_patchres=save_patchres, save_filebase=save_filebase, keep_patchres=keep_patchres)
|
|
842
|
+
|
|
843
|
+
# Catalog does not consist of patches
|
|
844
|
+
else:
|
|
845
|
+
|
|
846
|
+
if not dotomo_lens and self.zweighting:
|
|
847
|
+
print("Redshift-weighting requires tomographic computation for the lenses.")
|
|
848
|
+
dotomo_lens = True
|
|
849
|
+
|
|
850
|
+
if not dotomo_source:
|
|
851
|
+
self.nbinsz_source = 1
|
|
852
|
+
old_zbins_source = cat_source.zbins[:]
|
|
853
|
+
cat_source.zbins = np.zeros(cat_source.ngal, dtype=np.int32)
|
|
854
|
+
else:
|
|
855
|
+
self.nbinsz_source = cat_source.nbinsz
|
|
856
|
+
if not dotomo_lens:
|
|
857
|
+
self.nbinsz_lens = 1
|
|
858
|
+
old_zbins_lens = cat_lens.zbins[:]
|
|
859
|
+
cat_lens.zbins = np.zeros(cat_lens.ngal, dtype=np.int32)
|
|
860
|
+
else:
|
|
861
|
+
self.nbinsz_lens = cat_lens.nbinsz
|
|
862
|
+
|
|
863
|
+
if self.zweighting:
|
|
864
|
+
if cat_lens.zbins_mean is None:
|
|
865
|
+
print("Redshift-weighting requires information about mean redshift in tomo bins of lens catalog")
|
|
866
|
+
if cat_lens.zbins_std is None:
|
|
867
|
+
print("Warning: Redshift-dispersion in tomo bins of lens catalog not given. Set to zero.")
|
|
868
|
+
cat_lens.zbins_std = np.zeros(self.nbinsz_lens)
|
|
869
|
+
|
|
870
|
+
_z3combis = self.nbinsz_source*self.nbinsz_lens*self.nbinsz_lens
|
|
871
|
+
_r2combis = self.nbinsr*self.nbinsr
|
|
872
|
+
sc = (self.n_cfs, self.nmax+1, _z3combis, self.nbinsr, self.nbinsr)
|
|
873
|
+
sn = (self.nmax+1, _z3combis, self.nbinsr,self.nbinsr)
|
|
874
|
+
szr = (self.nbinsz_source, self.nbinsz_lens, self.nbinsr)
|
|
875
|
+
bin_centers = np.zeros(reduce(operator.mul, szr)).astype(np.float64)
|
|
876
|
+
Upsilon_n = np.zeros(reduce(operator.mul, sc)).astype(np.complex128)
|
|
877
|
+
Norm_n = np.zeros(reduce(operator.mul, sn)).astype(np.complex128)
|
|
878
|
+
args_sourcecat = (cat_source.isinner.astype(np.float64), cat_source.weight.astype(np.float64),
|
|
879
|
+
cat_source.pos1.astype(np.float64), cat_source.pos2.astype(np.float64),
|
|
880
|
+
cat_source.tracer_1.astype(np.float64), cat_source.tracer_2.astype(np.float64),
|
|
881
|
+
cat_source.zbins.astype(np.int32), np.int32(self.nbinsz_source), np.int32(cat_source.ngal), )
|
|
882
|
+
args_lenscat = (cat_lens.weight.astype(np.float64), cat_lens.pos1.astype(np.float64),
|
|
883
|
+
cat_lens.pos2.astype(np.float64), cat_lens.zbins.astype(np.int32),
|
|
884
|
+
np.int32(self.nbinsz_lens), np.int32(cat_lens.ngal), )
|
|
885
|
+
args_basesetup = (np.int32(self.nmax), np.float64(self.min_sep), np.float64(self.max_sep),
|
|
886
|
+
np.int32(self.nbinsr), np.int32(self.multicountcorr), )
|
|
887
|
+
if self.method=="Discrete":
|
|
888
|
+
hash_dpix = max(1.,self.max_sep//10.)
|
|
889
|
+
jointextent = list(cat_source._jointextent([cat_lens], extend=self.tree_resos[-1]))
|
|
890
|
+
cat_source.build_spatialhash(dpix=hash_dpix, extent=jointextent)
|
|
891
|
+
cat_lens.build_spatialhash(dpix=hash_dpix, extent=jointextent)
|
|
892
|
+
nregions = np.int32(len(np.argwhere(cat_source.index_matcher>-1).flatten()))
|
|
893
|
+
args_hash = (cat_source.index_matcher, cat_source.pixs_galind_bounds, cat_source.pix_gals,
|
|
894
|
+
cat_lens.index_matcher, cat_lens.pixs_galind_bounds, cat_lens.pix_gals, nregions, )
|
|
895
|
+
args_pixgrid = (np.float64(cat_lens.pix1_start), np.float64(cat_lens.pix1_d), np.int32(cat_lens.pix1_n),
|
|
896
|
+
np.float64(cat_lens.pix2_start), np.float64(cat_lens.pix2_d), np.int32(cat_lens.pix2_n), )
|
|
897
|
+
args = (*args_sourcecat,
|
|
898
|
+
*args_lenscat,
|
|
899
|
+
*args_basesetup,
|
|
900
|
+
*args_hash,
|
|
901
|
+
*args_pixgrid,
|
|
902
|
+
np.int32(self.nthreads),
|
|
903
|
+
np.int32(self._verbose_c),
|
|
904
|
+
bin_centers,
|
|
905
|
+
Upsilon_n,
|
|
906
|
+
Norm_n, )
|
|
907
|
+
func = self.clib.alloc_Gammans_discrete_GNN
|
|
908
|
+
if self.method == "DoubleTree":
|
|
909
|
+
cutfirst = np.int32(self.tree_resos[0]==0.)
|
|
910
|
+
jointextent = list(cat_source._jointextent([cat_lens], extend=self.tree_resos[-1]))
|
|
911
|
+
# Build multihashes for sources and lenses
|
|
912
|
+
mhash_source = cat_source.multihash(dpixs=self.tree_resos[cutfirst:], dpix_hash=self.tree_resos[-1],
|
|
913
|
+
shuffle=self.shuffle_pix, normed=True, extent=jointextent)
|
|
914
|
+
sngal_resos, spos1s, spos2s, sweights, szbins, sisinners, sallfields, sindex_matchers, \
|
|
915
|
+
spixs_galind_bounds, spix_gals, sdpixs1_true, sdpixs2_true = mhash_source
|
|
916
|
+
ngal_resos_source = np.array(sngal_resos, dtype=np.int32)
|
|
917
|
+
weight_resos_source = np.concatenate(sweights).astype(np.float64)
|
|
918
|
+
pos1_resos_source = np.concatenate(spos1s).astype(np.float64)
|
|
919
|
+
pos2_resos_source = np.concatenate(spos2s).astype(np.float64)
|
|
920
|
+
zbin_resos_source = np.concatenate(szbins).astype(np.int32)
|
|
921
|
+
isinner_resos_source = np.concatenate(sisinners).astype(np.float64)
|
|
922
|
+
e1_resos_source = np.concatenate([sallfields[i][0] for i in range(len(sallfields))]).astype(np.float64)
|
|
923
|
+
e2_resos_source = np.concatenate([sallfields[i][1] for i in range(len(sallfields))]).astype(np.float64)
|
|
924
|
+
index_matcher_source = np.concatenate(sindex_matchers).astype(np.int32)
|
|
925
|
+
pixs_galind_bounds_source = np.concatenate(spixs_galind_bounds).astype(np.int32)
|
|
926
|
+
pix_gals_source = np.concatenate(spix_gals).astype(np.int32)
|
|
927
|
+
mhash_lens = cat_lens.multihash(dpixs=self.tree_resos[cutfirst:], dpix_hash=self.tree_resos[-1],
|
|
928
|
+
shuffle=self.shuffle_pix, normed=True, extent=jointextent)
|
|
929
|
+
lngal_resos, lpos1s, lpos2s, lweights, lzbins, lisinners, lallfields, lindex_matchers, \
|
|
930
|
+
lpixs_galind_bounds, lpix_gals, ldpixs1_true, ldpixs2_true = mhash_lens
|
|
931
|
+
ngal_resos_lens = np.array(lngal_resos, dtype=np.int32)
|
|
932
|
+
weight_resos_lens = np.concatenate(lweights).astype(np.float64)
|
|
933
|
+
pos1_resos_lens = np.concatenate(lpos1s).astype(np.float64)
|
|
934
|
+
pos2_resos_lens = np.concatenate(lpos2s).astype(np.float64)
|
|
935
|
+
zbin_resos_lens = np.concatenate(lzbins).astype(np.int32)
|
|
936
|
+
isinner_resos_lens = np.concatenate(lisinners).astype(np.float64)
|
|
937
|
+
index_matcher_lens = np.concatenate(lindex_matchers).astype(np.int32)
|
|
938
|
+
pixs_galind_bounds_lens = np.concatenate(lpixs_galind_bounds).astype(np.int32)
|
|
939
|
+
pix_gals_lens = np.asarray(np.concatenate(lpix_gals)).astype(np.int32)
|
|
940
|
+
index_matcher_flat = np.argwhere(cat_source.index_matcher>-1).flatten().astype(np.int32)
|
|
941
|
+
nregions = np.int32(len(index_matcher_flat))
|
|
942
|
+
# Collect args
|
|
943
|
+
args_resoinfo = (np.int32(self.tree_nresos), np.int32(self.tree_nresos-cutfirst),
|
|
944
|
+
sdpixs1_true.astype(np.float64), sdpixs2_true.astype(np.float64), self.tree_redges, )
|
|
945
|
+
args_leafs = (np.int32(self.resoshift_leafs), np.int32(self.minresoind_leaf),
|
|
946
|
+
np.int32(self.maxresoind_leaf), )
|
|
947
|
+
args_resos = (isinner_resos_source, weight_resos_source, pos1_resos_source, pos2_resos_source,
|
|
948
|
+
e1_resos_source, e2_resos_source, zbin_resos_source, ngal_resos_source,
|
|
949
|
+
np.int32(self.nbinsz_source), isinner_resos_lens, weight_resos_lens, pos1_resos_lens,
|
|
950
|
+
pos2_resos_lens, zbin_resos_lens, ngal_resos_lens, np.int32(self.nbinsz_lens), )
|
|
951
|
+
args_mhash = (index_matcher_source, pixs_galind_bounds_source, pix_gals_source,
|
|
952
|
+
index_matcher_lens, pixs_galind_bounds_lens, pix_gals_lens, index_matcher_flat, nregions, )
|
|
953
|
+
args_pixgrid = (np.float64(cat_lens.pix1_start), np.float64(cat_lens.pix1_d), np.int32(cat_lens.pix1_n),
|
|
954
|
+
np.float64(cat_lens.pix2_start), np.float64(cat_lens.pix2_d), np.int32(cat_lens.pix2_n), )
|
|
955
|
+
args = (*args_resoinfo,
|
|
956
|
+
*args_leafs,
|
|
957
|
+
*args_resos,
|
|
958
|
+
*args_basesetup,
|
|
959
|
+
*args_mhash,
|
|
960
|
+
*args_pixgrid,
|
|
961
|
+
np.int32(self.nthreads),
|
|
962
|
+
np.int32(self._verbose_c),
|
|
963
|
+
bin_centers,
|
|
964
|
+
Upsilon_n,
|
|
965
|
+
Norm_n, )
|
|
966
|
+
func = self.clib.alloc_Gammans_doubletree_GNN
|
|
967
|
+
if self._verbose_debug:
|
|
968
|
+
for elarg, arg in enumerate(args):
|
|
969
|
+
toprint = (elarg, type(arg),)
|
|
970
|
+
if isinstance(arg, np.ndarray):
|
|
971
|
+
toprint += (type(arg[0]), arg.shape)
|
|
972
|
+
toprint += (func.argtypes[elarg], )
|
|
973
|
+
print(toprint)
|
|
974
|
+
print(arg)
|
|
975
|
+
|
|
976
|
+
func(*args)
|
|
977
|
+
|
|
978
|
+
self.bin_centers = bin_centers.reshape(szr)
|
|
979
|
+
self.bin_centers_mean = np.mean(self.bin_centers, axis=(0,1))
|
|
980
|
+
self.npcf_multipoles = np.nan_to_num(Upsilon_n.reshape(sc))
|
|
981
|
+
self.npcf_multipoles_norm = np.nan_to_num(Norm_n.reshape(sn))
|
|
982
|
+
self.projection = "X"
|
|
983
|
+
self.is_edge_corrected = False
|
|
984
|
+
|
|
985
|
+
if apply_edge_correction:
|
|
986
|
+
self.edge_correction()
|
|
987
|
+
|
|
988
|
+
if not dotomo_source:
|
|
989
|
+
cat_source.zbins = old_zbins_source
|
|
990
|
+
if not dotomo_lens:
|
|
991
|
+
cat_lens.zbins = old_zbins_lens
|
|
992
|
+
|
|
993
|
+
def edge_correction(self, ret_matrices=False):
|
|
994
|
+
assert(not self.is_edge_corrected)
|
|
995
|
+
def gen_M_matrix(thet1,thet2,threepcf_n_norm):
|
|
996
|
+
nvals, ntheta, _ = threepcf_n_norm.shape
|
|
997
|
+
nmax = (nvals-1)//2
|
|
998
|
+
narr = np.arange(-nmax,nmax+1, dtype=np.int)
|
|
999
|
+
nextM = np.zeros((nvals,nvals))
|
|
1000
|
+
for ind, ell in enumerate(narr):
|
|
1001
|
+
lminusn = ell-narr
|
|
1002
|
+
sel = np.logical_and(lminusn+nmax>=0, lminusn+nmax<nvals)
|
|
1003
|
+
nextM[ind,sel] = threepcf_n_norm[(lminusn+nmax)[sel],thet1,thet2].real / threepcf_n_norm[nmax,thet1,thet2].real
|
|
1004
|
+
return nextM
|
|
1005
|
+
|
|
1006
|
+
nvals, nzcombis, ntheta, _ = self.npcf_multipoles_norm.shape
|
|
1007
|
+
nmax = nvals-1
|
|
1008
|
+
threepcf_n_full = np.zeros((1,2*nmax+1, nzcombis, ntheta, ntheta), dtype=complex)
|
|
1009
|
+
threepcf_n_norm_full = np.zeros((2*nmax+1, nzcombis, ntheta, ntheta), dtype=complex)
|
|
1010
|
+
threepcf_n_corr = np.zeros(threepcf_n_full.shape, dtype=np.complex)
|
|
1011
|
+
threepcf_n_full[:,nmax:] = self.npcf_multipoles
|
|
1012
|
+
threepcf_n_norm_full[nmax:] = self.npcf_multipoles_norm
|
|
1013
|
+
for nextn in range(1,nvals):
|
|
1014
|
+
threepcf_n_full[0,nmax-nextn] = self.npcf_multipoles[0,nextn].transpose(0,2,1)
|
|
1015
|
+
threepcf_n_norm_full[nmax-nextn] = self.npcf_multipoles_norm[nextn].transpose(0,2,1)
|
|
1016
|
+
|
|
1017
|
+
if ret_matrices:
|
|
1018
|
+
mats = np.zeros((nzcombis,ntheta,ntheta,nvals,nvals))
|
|
1019
|
+
for indz in range(nzcombis):
|
|
1020
|
+
#sys.stdout.write("%i"%indz)
|
|
1021
|
+
for thet1 in range(ntheta):
|
|
1022
|
+
for thet2 in range(ntheta):
|
|
1023
|
+
nextM = gen_M_matrix(thet1,thet2,threepcf_n_norm_full[:,indz])
|
|
1024
|
+
nextM_inv = np.linalg.inv(nextM)
|
|
1025
|
+
if ret_matrices:
|
|
1026
|
+
mats[indz,thet1,thet2] = nextM
|
|
1027
|
+
threepcf_n_corr[0,:,indz,thet1,thet2] = np.matmul(nextM_inv,threepcf_n_full[0,:,indz,thet1,thet2])
|
|
1028
|
+
|
|
1029
|
+
self.npcf_multipoles = threepcf_n_corr[:,nmax:]
|
|
1030
|
+
self.is_edge_corrected = True
|
|
1031
|
+
|
|
1032
|
+
if ret_matrices:
|
|
1033
|
+
return threepcf_n_corr[:,nmax:], mats
|
|
1034
|
+
|
|
1035
|
+
# TODO:
|
|
1036
|
+
# * Include the z-weighting method
|
|
1037
|
+
# * Include the 2pcf as spline --> Should we also add an option to compute it here? Might be a mess
|
|
1038
|
+
# as then we also would need methods to properly distribute randoms...
|
|
1039
|
+
# * Do a voronoi-tesselation at the multipole level? Would be just 2D, but still might help? Eventually
|
|
1040
|
+
# bundle together cells s.t. tot_weight > theshold? However, this might then make the binning courser
|
|
1041
|
+
# for certain triangle configs(?)
|
|
1042
|
+
def multipoles2npcf(self, xi=None):
|
|
1043
|
+
r"""
|
|
1044
|
+
Notes
|
|
1045
|
+
-----
|
|
1046
|
+
* The Upsilon and Norms are only computed for the n>0 multipoles. The n<0 multipoles are recovered by symmetry considerations, i.e.:
|
|
1047
|
+
|
|
1048
|
+
.. math::
|
|
1049
|
+
|
|
1050
|
+
\Upsilon_{-n}(\theta_1, \theta_2, z_1, z_2, z_3) =
|
|
1051
|
+
\Upsilon_{n}(\theta_2, \theta_1, z_1, z_3, z_2)
|
|
1052
|
+
|
|
1053
|
+
As the tomographic bin combinations are interpreted as a flat list, they need to be appropriately shuffled. This is handled by ``ztiler``.
|
|
1054
|
+
|
|
1055
|
+
* When dividing by the (weighted) counts ``N``, all contributions for which ``N <= 0`` are set to zero.
|
|
1056
|
+
|
|
1057
|
+
"""
|
|
1058
|
+
_, nzcombis, rbins, rbins = np.shape(self.npcf_multipoles[0])
|
|
1059
|
+
self.npcf = np.zeros((self.n_cfs, nzcombis, rbins, rbins, len(self.phi)), dtype=complex)
|
|
1060
|
+
self.npcf_norm = np.zeros((nzcombis, rbins, rbins, len(self.phi)), dtype=float)
|
|
1061
|
+
ztiler = np.arange(self.nbinsz_source*self.nbinsz_lens*self.nbinsz_lens).reshape(
|
|
1062
|
+
(self.nbinsz_source,self.nbinsz_lens,self.nbinsz_lens)).transpose(0,2,1).flatten().astype(np.int32)
|
|
1063
|
+
|
|
1064
|
+
# 3PCF components
|
|
1065
|
+
conjmap = [0]
|
|
1066
|
+
N0 = 1./(2*np.pi) * self.npcf_multipoles_norm[0].astype(complex)
|
|
1067
|
+
for elm in range(self.n_cfs):
|
|
1068
|
+
for elphi, phi in enumerate(self.phi):
|
|
1069
|
+
tmp = 1./(2*np.pi) * self.npcf_multipoles[elm,0].astype(complex)
|
|
1070
|
+
for n in range(1,self.nmax+1):
|
|
1071
|
+
_const = 1./(2*np.pi) * np.exp(1J*n*phi)
|
|
1072
|
+
tmp += _const * self.npcf_multipoles[elm,n].astype(complex)
|
|
1073
|
+
tmp += _const.conj() * self.npcf_multipoles[conjmap[elm],n][ztiler].astype(complex).transpose(0,2,1)
|
|
1074
|
+
self.npcf[elm,...,elphi] = tmp
|
|
1075
|
+
# Normalization
|
|
1076
|
+
for elphi, phi in enumerate(self.phi):
|
|
1077
|
+
tmptotnorm = 1./(2*np.pi) * self.npcf_multipoles_norm[0].astype(complex)
|
|
1078
|
+
for n in range(1,self.nmax+1):
|
|
1079
|
+
_const = 1./(2*np.pi) * np.exp(1J*n*phi)
|
|
1080
|
+
tmptotnorm += _const * self.npcf_multipoles_norm[n].astype(complex)
|
|
1081
|
+
tmptotnorm += _const.conj() * self.npcf_multipoles_norm[n][ztiler].astype(complex).transpose(0,2,1)
|
|
1082
|
+
self.npcf_norm[...,elphi] = tmptotnorm.real
|
|
1083
|
+
|
|
1084
|
+
if self.is_edge_corrected:
|
|
1085
|
+
sel_zero = np.isnan(N0)
|
|
1086
|
+
_a = self.npcf
|
|
1087
|
+
_b = N0.real[:, :, np.newaxis]
|
|
1088
|
+
self.npcf = np.divide(_a, _b, out=np.zeros_like(_a), where=np.abs(_b)>0)
|
|
1089
|
+
else:
|
|
1090
|
+
_a = self.npcf
|
|
1091
|
+
_b = self.npcf_norm
|
|
1092
|
+
self.npcf = np.divide(_a, _b, out=np.zeros_like(_a), where=np.abs(_b)>0)
|
|
1093
|
+
#self.npcf = self.npcf/self.npcf_norm[0][None, ...].astype(complex)
|
|
1094
|
+
self.projection = "X"
|
|
1095
|
+
|
|
1096
|
+
# Optionally correct by clustering correlation function
|
|
1097
|
+
# Assume
|
|
1098
|
+
# xi[0] has shape (nbinsr_xi, )
|
|
1099
|
+
# xi[1] has shape (nbinsz_lens * nbinsz_lens, nbinsr_xi, )
|
|
1100
|
+
if xi is not None:
|
|
1101
|
+
assert(len(xi)==2)
|
|
1102
|
+
assert(xi[1].shape[1]==len(xi[0]))
|
|
1103
|
+
assert(xi[1].shape[0]==self.nbinsz_lens*self.nbinsz_lens)
|
|
1104
|
+
# Get angular separation at which xi is evaluated
|
|
1105
|
+
_rs1 = self.bin_centers_mean[:, None, None]
|
|
1106
|
+
_rs2 = self.bin_centers_mean[None, :, None]
|
|
1107
|
+
_phis = self.phi[None, None, :]
|
|
1108
|
+
d_xi = np.sqrt(_rs1**2 + _rs2**2 - 2*_rs1*_rs2*np.cos(_phis))
|
|
1109
|
+
xi_corr = interp1d(xi[0], xi[1], axis=-1,
|
|
1110
|
+
bounds_error=False, fill_value=0.0, kind="linear")(d_xi)
|
|
1111
|
+
# Apply correction to 3pcf (TODO: Looks a bit ugly...)
|
|
1112
|
+
_npcf = self.npcf[0].reshape((self.nbinsz_source, self.nbinsz_lens*self.nbinsz_lens, *d_xi.shape))
|
|
1113
|
+
_npcf *= (1.0 + xi_corr[None, ...])
|
|
1114
|
+
self.npcf[0] = _npcf.reshape(self.npcf[0].shape)
|
|
1115
|
+
|
|
1116
|
+
|
|
1117
|
+
## PROJECTIONS ##
|
|
1118
|
+
def projectnpcf(self, projection):
|
|
1119
|
+
super()._projectnpcf(self, projection)
|
|
1120
|
+
|
|
1121
|
+
## INTEGRATED MEASURES ##
|
|
1122
|
+
def computeNNM(self, radii, do_multiscale=False, xi=None, tofile=False, filtercache=None):
|
|
1123
|
+
"""
|
|
1124
|
+
Compute third-order aperture statistics using the polyonomial filter of Crittenden 2002.
|
|
1125
|
+
"""
|
|
1126
|
+
nb_config.NUMBA_DEFAULT_NUM_THREADS = self.nthreads
|
|
1127
|
+
nb_config.NUMBA_NUM_THREADS = self.nthreads
|
|
1128
|
+
|
|
1129
|
+
if self.npcf is None and self.npcf_multipoles is not None:
|
|
1130
|
+
self.multipoles2npcf(xi=xi)
|
|
1131
|
+
|
|
1132
|
+
nradii = len(radii)
|
|
1133
|
+
if not do_multiscale:
|
|
1134
|
+
nrcombis = nradii
|
|
1135
|
+
_rcut = 1
|
|
1136
|
+
else:
|
|
1137
|
+
nrcombis = nradii*nradii*nradii
|
|
1138
|
+
_rcut = nradii
|
|
1139
|
+
NNM = np.zeros((1, self.nbinsz_source*self.nbinsz_lens*self.nbinsz_lens, nrcombis), dtype=complex)
|
|
1140
|
+
tmprcombi = 0
|
|
1141
|
+
for elr1, R1 in enumerate(radii):
|
|
1142
|
+
for elr2, R2 in enumerate(radii[:_rcut]):
|
|
1143
|
+
for elr3, R3 in enumerate(radii[:_rcut]):
|
|
1144
|
+
if not do_multiscale:
|
|
1145
|
+
R2 = R1
|
|
1146
|
+
R3 = R1
|
|
1147
|
+
if filtercache is not None:
|
|
1148
|
+
A_NNM = filtercache[tmprcombi]
|
|
1149
|
+
else:
|
|
1150
|
+
A_NNM = self._NNM_filtergrid(R1, R2, R3)
|
|
1151
|
+
NNM[0,:,tmprcombi] = np.nansum(A_NNM*self.npcf[0,...],axis=(1,2,3))
|
|
1152
|
+
tmprcombi += 1
|
|
1153
|
+
return NNM
|
|
1154
|
+
|
|
1155
|
+
def _NNM_filtergrid(self, R1, R2, R3):
|
|
1156
|
+
return self.__NNM_filtergrid(R1, R2, R3, self.bin_edges, self.bin_centers_mean, self.phi)
|
|
1157
|
+
|
|
1158
|
+
@staticmethod
|
|
1159
|
+
@jit(nopython=True, parallel=True)
|
|
1160
|
+
def __NNM_filtergrid(R1, R2, R3, edges, centers, phis):
|
|
1161
|
+
nbinsr = len(centers)
|
|
1162
|
+
nbinsphi = len(phis)
|
|
1163
|
+
_cphis = np.cos(phis)
|
|
1164
|
+
_ephis = np.e**(1J*phis)
|
|
1165
|
+
_ephisc = np.e**(-1J*phis)
|
|
1166
|
+
Theta4 = 1./3. * (R1**2*R2**2 + R1**2*R3**2 + R2**2*R3**2)
|
|
1167
|
+
a2 = 2./3. * R1**2*R2**2*R3**2 / Theta4
|
|
1168
|
+
ANNM = np.zeros((nbinsr,nbinsr,nbinsphi), dtype=nb_complex128)
|
|
1169
|
+
for elb in prange(nbinsr*nbinsr):
|
|
1170
|
+
elb1 = int(elb//nbinsr)
|
|
1171
|
+
elb2 = elb%nbinsr
|
|
1172
|
+
_y1 = centers[elb1]
|
|
1173
|
+
_dbin1 = edges[elb1+1] - edges[elb1]
|
|
1174
|
+
_y2 = centers[elb2]
|
|
1175
|
+
_dbin2 = edges[elb2+1] - edges[elb2]
|
|
1176
|
+
_dbinphi = phis[1] - phis[0]
|
|
1177
|
+
b0 = _y1**2/(2*R1**2)+_y2**2/(2*R2**2) - a2/4.*(
|
|
1178
|
+
_y1**2/R1**4 + 2*_y1*_y2*_cphis/(R1**2*R2**2) + _y2**2/R2**4)
|
|
1179
|
+
g1 = _y1 - a2/2. * (_y1/R1**2 + _y2*_ephisc/R2**2)
|
|
1180
|
+
g2 = _y2 - a2/2. * (_y2/R2**2 + _y1*_ephis/R1**2)
|
|
1181
|
+
g1c = g1.conj()
|
|
1182
|
+
g2c = g2.conj()
|
|
1183
|
+
F1 = 2*R1**2 - g1*g1c
|
|
1184
|
+
F2 = 2*R2**2 - g2*g2c
|
|
1185
|
+
pref = np.e**(-b0)/(72*np.pi*Theta4**2)
|
|
1186
|
+
sum1 = (g1-_y1)*(g2-_y2) * (1/a2*F1*F2 - (F1+F2) + 2*a2 + g1c*g2*_ephisc + g1*g2c*_ephis)
|
|
1187
|
+
sum2 = ((g2-_y2) + (g1-_y1)*_ephis) * (g1*(F2-2*a2) + g2*(F1-2*a2)*_ephisc)
|
|
1188
|
+
sum3 = 2*g1*g2*a2
|
|
1189
|
+
_measures = _y1*_dbin1 * _y2*_dbin2 * _dbinphi
|
|
1190
|
+
ANNM[elb1,elb2] = _measures * pref * (sum1-sum2+sum3)
|
|
1191
|
+
|
|
1192
|
+
return ANNM
|
|
1193
|
+
|
|
1194
|
+
class NGGCorrelation(BinnedNPCF):
|
|
1195
|
+
r""" Class containing methods to measure and and obtain statistics that are built
|
|
1196
|
+
from third-order lens-shear-shear correlation functions.
|
|
1197
|
+
|
|
1198
|
+
Attributes
|
|
1199
|
+
----------
|
|
1200
|
+
min_sep: float
|
|
1201
|
+
The smallest distance of each vertex for which the NPCF is computed.
|
|
1202
|
+
max_sep: float
|
|
1203
|
+
The largest distance of each vertex for which the NPCF is computed.
|
|
1204
|
+
|
|
1205
|
+
Notes
|
|
1206
|
+
-----
|
|
1207
|
+
Inherits all other parameters and attributes from :class:`BinnedNPCF`.
|
|
1208
|
+
Additional child-specific parameters can be passed via ``kwargs``.
|
|
1209
|
+
Either ``nbinsr`` or ``binsize`` has to be provided to fix the binning scheme .
|
|
1210
|
+
|
|
1211
|
+
Note that the different components of the NGG correlator are ordered as
|
|
1212
|
+
|
|
1213
|
+
.. math::
|
|
1214
|
+
|
|
1215
|
+
\left[ \tilde{G}_-, \tilde{G}_+, \right] \ ,
|
|
1216
|
+
|
|
1217
|
+
which is different to the usual conventions, but matches orpheus' conventions to
|
|
1218
|
+
always start with a correlator in which not polar field is complex conjugated.
|
|
1219
|
+
"""
|
|
1220
|
+
def __init__(self, min_sep, max_sep, **kwargs):
|
|
1221
|
+
|
|
1222
|
+
super().__init__(3, [0,2,2], n_cfs=2, min_sep=min_sep, max_sep=max_sep, **kwargs)
|
|
1223
|
+
self.nmax = self.nmaxs[0]
|
|
1224
|
+
self.phi = self.phis[0]
|
|
1225
|
+
self.projection = None
|
|
1226
|
+
self.projections_avail = [None, "X"]
|
|
1227
|
+
self.nbinsz_source = None
|
|
1228
|
+
self.nbinsz_lens = None
|
|
1229
|
+
|
|
1230
|
+
# (Add here any newly implemented projections)
|
|
1231
|
+
self._initprojections(self)
|
|
1232
|
+
|
|
1233
|
+
def __process_patches(self, cat_source, cat_lens, dotomo_source=True, dotomo_lens=True, rotsignflip=False,
|
|
1234
|
+
apply_edge_correction=False, save_patchres=False, save_filebase="", keep_patchres=False):
|
|
1235
|
+
if save_patchres:
|
|
1236
|
+
if not Path(save_patchres).is_dir():
|
|
1237
|
+
raise ValueError('Path to directory does not exist.')
|
|
1238
|
+
|
|
1239
|
+
for elp in range(cat_source.npatches):
|
|
1240
|
+
if self._verbose_python:
|
|
1241
|
+
print('Doing patch %i/%i'%(elp+1,cat_source.npatches))
|
|
1242
|
+
# Compute statistics on patch
|
|
1243
|
+
pscat = cat_source.frompatchind(elp,rotsignflip=rotsignflip)
|
|
1244
|
+
plcat = cat_lens.frompatchind(elp)
|
|
1245
|
+
pcorr = NGGCorrelation(
|
|
1246
|
+
min_sep=self.min_sep,
|
|
1247
|
+
max_sep=self.max_sep,
|
|
1248
|
+
nbinsr=self.nbinsr,
|
|
1249
|
+
nbinsphi=self.nbinsphi,
|
|
1250
|
+
nmaxs=self.nmaxs,
|
|
1251
|
+
method=self.method,
|
|
1252
|
+
multicountcorr=self.multicountcorr,
|
|
1253
|
+
shuffle_pix=self.shuffle_pix,
|
|
1254
|
+
tree_resos=self.tree_resos,
|
|
1255
|
+
rmin_pixsize=self.rmin_pixsize,
|
|
1256
|
+
resoshift_leafs=self.resoshift_leafs,
|
|
1257
|
+
minresoind_leaf=self.minresoind_leaf,
|
|
1258
|
+
maxresoind_leaf=self.maxresoind_leaf,
|
|
1259
|
+
nthreads=self.nthreads,
|
|
1260
|
+
verbosity=self.verbosity)
|
|
1261
|
+
pcorr.process(pscat, plcat, dotomo_source=dotomo_source, dotomo_lens=dotomo_lens)
|
|
1262
|
+
|
|
1263
|
+
# Update the total measurement
|
|
1264
|
+
if elp == 0:
|
|
1265
|
+
self.nbinsz_source = pcorr.nbinsz_source
|
|
1266
|
+
self.nbinsz_lens = pcorr.nbinsz_lens
|
|
1267
|
+
self.bin_centers = np.zeros_like(pcorr.bin_centers)
|
|
1268
|
+
self.npcf_multipoles = np.zeros_like(pcorr.npcf_multipoles)
|
|
1269
|
+
self.npcf_multipoles_norm = np.zeros_like(pcorr.npcf_multipoles_norm)
|
|
1270
|
+
_footnorm = np.zeros_like(pcorr.bin_centers)
|
|
1271
|
+
if keep_patchres:
|
|
1272
|
+
centers_patches = np.zeros((cat_source.npatches, *pcorr.bin_centers.shape), dtype=pcorr.bin_centers.dtype)
|
|
1273
|
+
npcf_multipoles_patches = np.zeros((cat_source.npatches, *pcorr.npcf_multipoles.shape), dtype=pcorr.npcf_multipoles.dtype)
|
|
1274
|
+
npcf_multipoles_norm_patches = np.zeros((cat_source.npatches, *pcorr.npcf_multipoles_norm.shape), dtype=pcorr.npcf_multipoles_norm.dtype)
|
|
1275
|
+
_shelltriplets = np.array([[[pcorr.npcf_multipoles_norm[pcorr.nmaxs[0],zl*self.nbinsz_source*self.nbinsz_source+zs*self.nbinsz_source+zs,i,i].real
|
|
1276
|
+
for i in range(pcorr.nbinsr)] for zs in range(self.nbinsz_source)] for zl in range(self.nbinsz_lens)])
|
|
1277
|
+
# Rough estimate of scaling of pair counts based on zeroth multipole of triplets. Note that we might get nans here due to numerical
|
|
1278
|
+
# inaccuracies in the multiple counting corrections for bins with zero triplets, so we force those values to be zero.
|
|
1279
|
+
_patchnorm = np.nan_to_num(np.sqrt(_shelltriplets))
|
|
1280
|
+
self.bin_centers += _patchnorm*pcorr.bin_centers
|
|
1281
|
+
_footnorm += _patchnorm
|
|
1282
|
+
self.npcf_multipoles += pcorr.npcf_multipoles
|
|
1283
|
+
self.npcf_multipoles_norm += pcorr.npcf_multipoles_norm
|
|
1284
|
+
if keep_patchres:
|
|
1285
|
+
centers_patches[elp] += pcorr.bin_centers
|
|
1286
|
+
npcf_multipoles_patches[elp] += pcorr.npcf_multipoles
|
|
1287
|
+
npcf_multipoles_norm_patches[elp] += pcorr.npcf_multipoles_norm
|
|
1288
|
+
if save_patchres:
|
|
1289
|
+
pcorr.saveinst(save_patchres, save_filebase+'_patch%i'%elp)
|
|
1290
|
+
|
|
1291
|
+
# Finalize the measurement on the full footprint
|
|
1292
|
+
self.bin_centers = np.divide(self.bin_centers,_footnorm, out=np.zeros_like(self.bin_centers), where=_footnorm>0)
|
|
1293
|
+
self.bin_centers_mean =np.mean(self.bin_centers, axis=(0,1))
|
|
1294
|
+
self.projection = "X"
|
|
1295
|
+
|
|
1296
|
+
if keep_patchres:
|
|
1297
|
+
return centers_patches, npcf_multipoles_patches, npcf_multipoles_norm_patches
|
|
1298
|
+
|
|
1299
|
+
def process(self, cat_source, cat_lens, dotomo_source=True, dotomo_lens=True, rotsignflip=False, apply_edge_correction=False,
|
|
1300
|
+
save_patchres=False, save_filebase="", keep_patchres=False):
|
|
1301
|
+
r"""
|
|
1302
|
+
Compute a lens-shear-shear correlation provided a source and a lens catalog.
|
|
1303
|
+
|
|
1304
|
+
Parameters
|
|
1305
|
+
----------
|
|
1306
|
+
cat_source: orpheus.SpinTracerCatalog
|
|
1307
|
+
The source catalog which is processed
|
|
1308
|
+
cat_lens: orpheus.ScalarTracerCatalog
|
|
1309
|
+
The lens catalog which is processed
|
|
1310
|
+
dotomo_source: bool
|
|
1311
|
+
Flag that decides whether the tomographic information in the source catalog should be used. Defaults to `True`.
|
|
1312
|
+
dotomo_lens: bool
|
|
1313
|
+
Flag that decides whether the tomographic information in the lens catalog should be used. Defaults to `True`.
|
|
1314
|
+
rotsignflip: bool
|
|
1315
|
+
If the shape catalog is has been decomposed in patches, choose whether the rotation angle should be flipped.
|
|
1316
|
+
For simulated data this was always ok to set to 'False`. Defaults to `False`.
|
|
1317
|
+
apply_edge_correction: bool
|
|
1318
|
+
Flag that decides how the NPCF in the real space basis is computed.
|
|
1319
|
+
* If set to `True` the computation is done via edge-correcting the GNN-multipoles
|
|
1320
|
+
* If set to `False` both GNN and NNN are transformed separately and the ratio is done in the real-space basis
|
|
1321
|
+
Defaults to `False`.
|
|
1322
|
+
save_patchres: bool or str
|
|
1323
|
+
If the shape catalog is has been decomposed in patches, flag whether to save the GG measurements on the individual patches.
|
|
1324
|
+
Note that the path needs to exist, otherwise a `ValueError` is raised. For a flat-sky catalog this parameter
|
|
1325
|
+
has no effect. Defaults to `False`
|
|
1326
|
+
save_filebase: str
|
|
1327
|
+
Base of the filenames in which the patches are saved. The full filename will be `<save_patchres>/<save_filebase>_patchxx.npz`.
|
|
1328
|
+
Only has an effect if the shape catalog consists of multiple patches and `save_patchres` is not `False`.
|
|
1329
|
+
keep_patchres: bool
|
|
1330
|
+
If the catalog consists of multiple patches, returns all measurements on the patches. Defaults to `False`.
|
|
1331
|
+
"""
|
|
1332
|
+
|
|
1333
|
+
self._checkcats([cat_lens, cat_source, cat_source], [0, 2, 2])
|
|
1334
|
+
|
|
1335
|
+
# Catch typical errors, i.e. incompatible catalogs or missin patch decompositions
|
|
1336
|
+
if cat_source.geometry=='spherical' and cat_source.patchinds is None:
|
|
1337
|
+
raise ValueError('Error: Spherical catalog needs to be first decomposed into patches using the Catalog._topatches method.')
|
|
1338
|
+
if cat_lens.geometry=='spherical' and cat_lens.patchinds is None:
|
|
1339
|
+
raise ValueError('Error: Spherical catalog needs to be first decomposed into patches using the Catalog._topatches method.')
|
|
1340
|
+
if cat_source.geometry != cat_lens.geometry:
|
|
1341
|
+
raise ValueError('Incompatible geometries of source catalog (%s) and lens catalog (%s).'%(
|
|
1342
|
+
cat_source.geometry,cat_lens.geometry))
|
|
1343
|
+
|
|
1344
|
+
# Catalog consist of multiple patches
|
|
1345
|
+
if (cat_source.patchinds is not None) and (cat_lens.patchinds is not None):
|
|
1346
|
+
return self.__process_patches(cat_source, cat_lens, dotomo_source=dotomo_source, dotomo_lens=dotomo_lens,
|
|
1347
|
+
rotsignflip=rotsignflip, apply_edge_correction=apply_edge_correction,
|
|
1348
|
+
save_patchres=save_patchres, save_filebase=save_filebase, keep_patchres=keep_patchres)
|
|
1349
|
+
|
|
1350
|
+
# Catalog does not consist of patches
|
|
1351
|
+
else:
|
|
1352
|
+
if not dotomo_source:
|
|
1353
|
+
self.nbinsz_source = 1
|
|
1354
|
+
old_zbins_source = cat_source.zbins[:]
|
|
1355
|
+
cat_source.zbins = np.zeros(cat_source.ngal, dtype=np.int32)
|
|
1356
|
+
else:
|
|
1357
|
+
self.nbinsz_source = cat_source.nbinsz
|
|
1358
|
+
if not dotomo_lens:
|
|
1359
|
+
self.nbinsz_lens = 1
|
|
1360
|
+
old_zbins_lens = cat_lens.zbins[:]
|
|
1361
|
+
cat_lens.zbins = np.zeros(cat_lens.ngal, dtype=np.int32)
|
|
1362
|
+
else:
|
|
1363
|
+
self.nbinsz_lens = cat_lens.nbinsz
|
|
1364
|
+
|
|
1365
|
+
_z3combis = self.nbinsz_lens*self.nbinsz_source*self.nbinsz_source
|
|
1366
|
+
_r2combis = self.nbinsr*self.nbinsr
|
|
1367
|
+
sc = (self.n_cfs, 2*self.nmax+1, _z3combis, self.nbinsr, self.nbinsr)
|
|
1368
|
+
sn = (2*self.nmax+1, _z3combis, self.nbinsr,self.nbinsr)
|
|
1369
|
+
szr = (self.nbinsz_lens, self.nbinsz_source, self.nbinsr)
|
|
1370
|
+
bin_centers = np.zeros(reduce(operator.mul, szr)).astype(np.float64)
|
|
1371
|
+
Upsilon_n = np.zeros(reduce(operator.mul, sc)).astype(np.complex128)
|
|
1372
|
+
Norm_n = np.zeros(reduce(operator.mul, sn)).astype(np.complex128)
|
|
1373
|
+
args_sourcecat = (cat_source.weight.astype(np.float64),
|
|
1374
|
+
cat_source.pos1.astype(np.float64), cat_source.pos2.astype(np.float64),
|
|
1375
|
+
cat_source.tracer_1.astype(np.float64), cat_source.tracer_2.astype(np.float64),
|
|
1376
|
+
cat_source.zbins.astype(np.int32), np.int32(self.nbinsz_source), np.int32(cat_source.ngal), )
|
|
1377
|
+
args_lenscat = (cat_lens.isinner.astype(np.float64), cat_lens.weight.astype(np.float64), cat_lens.pos1.astype(np.float64),
|
|
1378
|
+
cat_lens.pos2.astype(np.float64), cat_lens.zbins.astype(np.int32),
|
|
1379
|
+
np.int32(self.nbinsz_lens), np.int32(cat_lens.ngal), )
|
|
1380
|
+
args_basesetup = (np.int32(self.nmax), np.float64(self.min_sep), np.float64(self.max_sep),
|
|
1381
|
+
np.int32(self.nbinsr), np.int32(self.multicountcorr), )
|
|
1382
|
+
if self.method=="Discrete":
|
|
1383
|
+
hash_dpix = max(1.,self.max_sep//10.)
|
|
1384
|
+
jointextent = list(cat_source._jointextent([cat_lens], extend=self.tree_resos[-1]))
|
|
1385
|
+
cat_source.build_spatialhash(dpix=hash_dpix, extent=jointextent)
|
|
1386
|
+
cat_lens.build_spatialhash(dpix=hash_dpix, extent=jointextent)
|
|
1387
|
+
nregions = np.int32(len(np.argwhere(cat_lens.index_matcher>-1).flatten()))
|
|
1388
|
+
args_hash = (cat_source.index_matcher, cat_source.pixs_galind_bounds, cat_source.pix_gals,
|
|
1389
|
+
cat_lens.index_matcher, cat_lens.pixs_galind_bounds, cat_lens.pix_gals, nregions, )
|
|
1390
|
+
args_pixgrid = (np.float64(cat_lens.pix1_start), np.float64(cat_lens.pix1_d), np.int32(cat_lens.pix1_n),
|
|
1391
|
+
np.float64(cat_lens.pix2_start), np.float64(cat_lens.pix2_d), np.int32(cat_lens.pix2_n), )
|
|
1392
|
+
args = (*args_sourcecat,
|
|
1393
|
+
*args_lenscat,
|
|
1394
|
+
*args_basesetup,
|
|
1395
|
+
*args_hash,
|
|
1396
|
+
*args_pixgrid,
|
|
1397
|
+
np.int32(self.nthreads),
|
|
1398
|
+
np.int32(self._verbose_c),
|
|
1399
|
+
bin_centers,
|
|
1400
|
+
Upsilon_n,
|
|
1401
|
+
Norm_n, )
|
|
1402
|
+
func = self.clib.alloc_Gammans_discrete_NGG
|
|
1403
|
+
if self.method=="Tree" or self.method == "DoubleTree":
|
|
1404
|
+
cutfirst = np.int32(self.tree_resos[0]==0.)
|
|
1405
|
+
jointextent = list(cat_source._jointextent([cat_lens], extend=self.tree_resos[-1]))
|
|
1406
|
+
# Build multihashes for sources and lenses
|
|
1407
|
+
mhash_source = cat_source.multihash(dpixs=self.tree_resos[cutfirst:], dpix_hash=self.tree_resos[-1],
|
|
1408
|
+
shuffle=self.shuffle_pix, normed=True, extent=jointextent)
|
|
1409
|
+
sngal_resos, spos1s, spos2s, sweights, szbins, sisinners, sallfields, sindex_matchers, \
|
|
1410
|
+
spixs_galind_bounds, spix_gals, sdpixs1_true, sdpixs2_true = mhash_source
|
|
1411
|
+
ngal_resos_source = np.array(sngal_resos, dtype=np.int32)
|
|
1412
|
+
weight_resos_source = np.concatenate(sweights).astype(np.float64)
|
|
1413
|
+
pos1_resos_source = np.concatenate(spos1s).astype(np.float64)
|
|
1414
|
+
pos2_resos_source = np.concatenate(spos2s).astype(np.float64)
|
|
1415
|
+
zbin_resos_source = np.concatenate(szbins).astype(np.int32)
|
|
1416
|
+
isinner_resos_source = np.concatenate(sisinners).astype(np.float64)
|
|
1417
|
+
e1_resos_source = np.concatenate([sallfields[i][0] for i in range(len(sallfields))]).astype(np.float64)
|
|
1418
|
+
e2_resos_source = np.concatenate([sallfields[i][1] for i in range(len(sallfields))]).astype(np.float64)
|
|
1419
|
+
index_matcher_source = np.concatenate(sindex_matchers).astype(np.int32)
|
|
1420
|
+
pixs_galind_bounds_source = np.concatenate(spixs_galind_bounds).astype(np.int32)
|
|
1421
|
+
pix_gals_source = np.concatenate(spix_gals).astype(np.int32)
|
|
1422
|
+
mhash_lens = cat_lens.multihash(dpixs=self.tree_resos[cutfirst:], dpix_hash=self.tree_resos[-1],
|
|
1423
|
+
shuffle=self.shuffle_pix, normed=True, extent=jointextent)
|
|
1424
|
+
lngal_resos, lpos1s, lpos2s, lweights, lzbins, lisinners, lallfields, lindex_matchers, \
|
|
1425
|
+
lpixs_galind_bounds, lpix_gals, ldpixs1_true, ldpixs2_true = mhash_lens
|
|
1426
|
+
ngal_resos_lens = np.array(lngal_resos, dtype=np.int32)
|
|
1427
|
+
weight_resos_lens = np.concatenate(lweights).astype(np.float64)
|
|
1428
|
+
pos1_resos_lens = np.concatenate(lpos1s).astype(np.float64)
|
|
1429
|
+
pos2_resos_lens = np.concatenate(lpos2s).astype(np.float64)
|
|
1430
|
+
zbin_resos_lens = np.concatenate(lzbins).astype(np.int32)
|
|
1431
|
+
isinner_resos_lens = np.concatenate(lisinners).astype(np.float64)
|
|
1432
|
+
index_matcher_lens = np.concatenate(lindex_matchers).astype(np.int32)
|
|
1433
|
+
pixs_galind_bounds_lens = np.concatenate(lpixs_galind_bounds).astype(np.int32)
|
|
1434
|
+
pix_gals_lens = np.asarray(np.concatenate(lpix_gals)).astype(np.int32)
|
|
1435
|
+
index_matcher_flat = np.argwhere(cat_lens.index_matcher>-1).flatten().astype(np.int32)
|
|
1436
|
+
nregions = np.int32(len(index_matcher_flat))
|
|
1437
|
+
if self.method=="Tree":
|
|
1438
|
+
# Collect args
|
|
1439
|
+
args_resoinfo = (np.int32(self.tree_nresos), self.tree_redges,)
|
|
1440
|
+
args_resos_sourcecat = (weight_resos_source, pos1_resos_source, pos2_resos_source,
|
|
1441
|
+
e1_resos_source, e2_resos_source, zbin_resos_source,
|
|
1442
|
+
np.int32(self.nbinsz_source), ngal_resos_source, )
|
|
1443
|
+
args_mhash = (index_matcher_source, pixs_galind_bounds_source, pix_gals_source,
|
|
1444
|
+
index_matcher_lens, pixs_galind_bounds_lens, pix_gals_lens, nregions, )
|
|
1445
|
+
args_pixgrid = (np.float64(cat_lens.pix1_start), np.float64(cat_lens.pix1_d), np.int32(cat_lens.pix1_n),
|
|
1446
|
+
np.float64(cat_lens.pix2_start), np.float64(cat_lens.pix2_d), np.int32(cat_lens.pix2_n), )
|
|
1447
|
+
args = (*args_resoinfo,
|
|
1448
|
+
*args_resos_sourcecat,
|
|
1449
|
+
*args_lenscat,
|
|
1450
|
+
*args_mhash,
|
|
1451
|
+
*args_pixgrid,
|
|
1452
|
+
*args_basesetup,
|
|
1453
|
+
np.int32(self.nthreads),
|
|
1454
|
+
np.int32(self._verbose_c),
|
|
1455
|
+
bin_centers,
|
|
1456
|
+
Upsilon_n,
|
|
1457
|
+
Norm_n, )
|
|
1458
|
+
func = self.clib.alloc_Gammans_tree_NGG
|
|
1459
|
+
if self.method == "DoubleTree":
|
|
1460
|
+
# Collect args
|
|
1461
|
+
args_resoinfo = (np.int32(self.tree_nresos), np.int32(self.tree_nresos-cutfirst),
|
|
1462
|
+
sdpixs1_true.astype(np.float64), sdpixs2_true.astype(np.float64), self.tree_redges, )
|
|
1463
|
+
args_leafs = (np.int32(self.resoshift_leafs), np.int32(self.minresoind_leaf),
|
|
1464
|
+
np.int32(self.maxresoind_leaf), )
|
|
1465
|
+
args_resos = (isinner_resos_source, weight_resos_source, pos1_resos_source, pos2_resos_source,
|
|
1466
|
+
e1_resos_source, e2_resos_source, zbin_resos_source, ngal_resos_source,
|
|
1467
|
+
np.int32(self.nbinsz_source), isinner_resos_lens, weight_resos_lens, pos1_resos_lens,
|
|
1468
|
+
pos2_resos_lens, zbin_resos_lens, ngal_resos_lens, np.int32(self.nbinsz_lens), )
|
|
1469
|
+
args_mhash = (index_matcher_source, pixs_galind_bounds_source, pix_gals_source,
|
|
1470
|
+
index_matcher_lens, pixs_galind_bounds_lens, pix_gals_lens, index_matcher_flat, nregions, )
|
|
1471
|
+
args_pixgrid = (np.float64(cat_lens.pix1_start), np.float64(cat_lens.pix1_d), np.int32(cat_lens.pix1_n),
|
|
1472
|
+
np.float64(cat_lens.pix2_start), np.float64(cat_lens.pix2_d), np.int32(cat_lens.pix2_n), )
|
|
1473
|
+
args = (*args_resoinfo,
|
|
1474
|
+
*args_leafs,
|
|
1475
|
+
*args_resos,
|
|
1476
|
+
*args_basesetup,
|
|
1477
|
+
*args_mhash,
|
|
1478
|
+
*args_pixgrid,
|
|
1479
|
+
np.int32(self.nthreads),
|
|
1480
|
+
np.int32(self._verbose_c),
|
|
1481
|
+
bin_centers,
|
|
1482
|
+
Upsilon_n,
|
|
1483
|
+
Norm_n, )
|
|
1484
|
+
func = self.clib.alloc_Gammans_doubletree_NGG
|
|
1485
|
+
if self._verbose_debug:
|
|
1486
|
+
for elarg, arg in enumerate(args):
|
|
1487
|
+
toprint = (elarg, type(arg),)
|
|
1488
|
+
if isinstance(arg, np.ndarray):
|
|
1489
|
+
toprint += (type(arg[0]), arg.shape)
|
|
1490
|
+
try:
|
|
1491
|
+
toprint += (func.argtypes[elarg], )
|
|
1492
|
+
print(toprint)
|
|
1493
|
+
print(arg)
|
|
1494
|
+
except:
|
|
1495
|
+
print("We did have a problem for arg %i"%elarg)
|
|
1496
|
+
|
|
1497
|
+
func(*args)
|
|
1498
|
+
|
|
1499
|
+
# Components of npcf are ordered as (Ups_-, Ups_+)
|
|
1500
|
+
self.bin_centers = bin_centers.reshape(szr)
|
|
1501
|
+
self.bin_centers_mean = np.mean(self.bin_centers, axis=(0,1))
|
|
1502
|
+
self.npcf_multipoles = Upsilon_n.reshape(sc)
|
|
1503
|
+
self.npcf_multipoles_norm = Norm_n.reshape(sn)
|
|
1504
|
+
self.projection = "X"
|
|
1505
|
+
self.is_edge_corrected = False
|
|
1506
|
+
|
|
1507
|
+
if apply_edge_correction:
|
|
1508
|
+
self.edge_correction()
|
|
1509
|
+
|
|
1510
|
+
if not dotomo_source:
|
|
1511
|
+
cat_source.zbins = old_zbins_source
|
|
1512
|
+
if not dotomo_lens:
|
|
1513
|
+
cat_lens.zbins = old_zbins_lens
|
|
1514
|
+
|
|
1515
|
+
def edge_correction(self, ret_matrices=False):
|
|
1516
|
+
|
|
1517
|
+
assert(not self.is_edge_corrected)
|
|
1518
|
+
def gen_M_matrix(thet1,thet2,threepcf_n_norm):
|
|
1519
|
+
nvals, ntheta, _ = threepcf_n_norm.shape
|
|
1520
|
+
nmax = (nvals-1)//2
|
|
1521
|
+
narr = np.arange(-nmax,nmax+1, dtype=np.int)
|
|
1522
|
+
nextM = np.zeros((nvals,nvals))
|
|
1523
|
+
for ind, ell in enumerate(narr):
|
|
1524
|
+
lminusn = ell-narr
|
|
1525
|
+
sel = np.logical_and(lminusn+nmax>=0, lminusn+nmax<nvals)
|
|
1526
|
+
nextM[ind,sel] = threepcf_n_norm[(lminusn+nmax)[sel],thet1,thet2].real / threepcf_n_norm[nmax,thet1,thet2].real
|
|
1527
|
+
return nextM
|
|
1528
|
+
|
|
1529
|
+
_nvals, nzcombis, ntheta, _ = self.npcf_multipoles_norm.shape
|
|
1530
|
+
nvals = int((_nvals-1)/2)
|
|
1531
|
+
nmax = nvals-1
|
|
1532
|
+
threepcf_n_corr = np.zeros_like(self.npcf_multipoles)
|
|
1533
|
+
if ret_matrices:
|
|
1534
|
+
mats = np.zeros((nzcombis,ntheta,ntheta,nvals,nvals))
|
|
1535
|
+
for indz in range(nzcombis):
|
|
1536
|
+
#sys.stdout.write("%i"%indz)
|
|
1537
|
+
for thet1 in range(ntheta):
|
|
1538
|
+
for thet2 in range(ntheta):
|
|
1539
|
+
nextM = gen_M_matrix(thet1,thet2,self.npcf_multipoles_norm[:,indz])
|
|
1540
|
+
nextM_inv = np.linalg.inv(nextM)
|
|
1541
|
+
if ret_matrices:
|
|
1542
|
+
mats[indz,thet1,thet2] = nextM
|
|
1543
|
+
for el_cf in range(self.n_cfs):
|
|
1544
|
+
threepcf_n_corr[el_cf,:,indz,thet1,thet2] = np.matmul(
|
|
1545
|
+
nextM_inv,self.npcf_multipoles[el_cf,:,indz,thet1,thet2])
|
|
1546
|
+
|
|
1547
|
+
self.npcf_multipoles = threepcf_n_corr
|
|
1548
|
+
self.is_edge_corrected = True
|
|
1549
|
+
|
|
1550
|
+
if ret_matrices:
|
|
1551
|
+
return threepcf_n_corr, mats
|
|
1552
|
+
|
|
1553
|
+
def multipoles2npcf(self, integrated=False):
|
|
1554
|
+
r"""
|
|
1555
|
+
Notes
|
|
1556
|
+
-----
|
|
1557
|
+
* When dividing by the (weighted) counts ``N``, all contributions for which ``N <= 0`` are set to zero.
|
|
1558
|
+
|
|
1559
|
+
"""
|
|
1560
|
+
_, nzcombis, rbins, rbins = np.shape(self.npcf_multipoles[0])
|
|
1561
|
+
self.npcf = np.zeros((self.n_cfs, nzcombis, rbins, rbins, len(self.phi)), dtype=complex)
|
|
1562
|
+
self.npcf_norm = np.zeros((nzcombis, rbins, rbins, len(self.phi)), dtype=float)
|
|
1563
|
+
ztiler = np.arange(self.nbinsz_lens*self.nbinsz_source*self.nbinsz_source).reshape(
|
|
1564
|
+
(self.nbinsz_lens,self.nbinsz_source,self.nbinsz_source)).transpose(0,2,1).flatten().astype(np.int32)
|
|
1565
|
+
|
|
1566
|
+
# NGG components
|
|
1567
|
+
for elphi, phi in enumerate(self.phi):
|
|
1568
|
+
tmp = np.zeros((self.n_cfs, nzcombis, rbins, rbins),dtype=complex)
|
|
1569
|
+
tmpnorm = np.zeros((nzcombis, rbins, rbins),dtype=complex)
|
|
1570
|
+
for n in range(2*self.nmax+1):
|
|
1571
|
+
dphi = self.phi[1] - self.phi[0]
|
|
1572
|
+
if integrated:
|
|
1573
|
+
if n==self.nmax:
|
|
1574
|
+
ifac = dphi
|
|
1575
|
+
else:
|
|
1576
|
+
ifac = 2./(n-self.nmax) * np.sin((n-self.nmax)*dphi/2.)
|
|
1577
|
+
else:
|
|
1578
|
+
ifac = dphi
|
|
1579
|
+
_const = 1./(2*np.pi) * np.exp(1J*(n-self.nmax)*phi) * ifac
|
|
1580
|
+
tmpnorm += _const * self.npcf_multipoles_norm[n].astype(complex)
|
|
1581
|
+
for el_cf in range(self.n_cfs):
|
|
1582
|
+
tmp[el_cf] += _const * self.npcf_multipoles[el_cf,n].astype(complex)
|
|
1583
|
+
self.npcf[...,elphi] = tmp
|
|
1584
|
+
self.npcf_norm[...,elphi] = tmpnorm.real
|
|
1585
|
+
|
|
1586
|
+
if self.is_edge_corrected:
|
|
1587
|
+
N0 = dphi/(2*np.pi) * self.npcf_multipoles_norm[self.nmax].astype(complex)
|
|
1588
|
+
sel_zero = np.isnan(N0)
|
|
1589
|
+
_a = self.npcf
|
|
1590
|
+
_b = N0.real[np.newaxis, :, :, :, np.newaxis]
|
|
1591
|
+
self.npcf = np.divide(_a, _b, out=np.zeros_like(_a), where=_b>0)
|
|
1592
|
+
else:
|
|
1593
|
+
_a = self.npcf
|
|
1594
|
+
_b = self.npcf_norm
|
|
1595
|
+
self.npcf = np.divide(_a, _b, out=np.zeros_like(_a), where=_b>0)
|
|
1596
|
+
#self.npcf = self.npcf/self.npcf_norm[0][None, ...].astype(complex)
|
|
1597
|
+
self.projection = "X"
|
|
1598
|
+
|
|
1599
|
+
## PROJECTIONS ##
|
|
1600
|
+
def projectnpcf(self, projection):
|
|
1601
|
+
super()._projectnpcf(self, projection)
|
|
1602
|
+
|
|
1603
|
+
## INTEGRATED MEASURES ##
|
|
1604
|
+
def computeNMM(self, radii, do_multiscale=False, tofile=False, filtercache=None):
|
|
1605
|
+
"""
|
|
1606
|
+
Compute third-order aperture statistics
|
|
1607
|
+
"""
|
|
1608
|
+
|
|
1609
|
+
nb_config.NUMBA_DEFAULT_NUM_THREADS = self.nthreads
|
|
1610
|
+
nb_config.NUMBA_NUM_THREADS = self.nthreads
|
|
1611
|
+
|
|
1612
|
+
if self.npcf is None and self.npcf_multipoles is not None:
|
|
1613
|
+
self.multipoles2npcf()
|
|
1614
|
+
|
|
1615
|
+
nradii = len(radii)
|
|
1616
|
+
if not do_multiscale:
|
|
1617
|
+
nrcombis = nradii
|
|
1618
|
+
_rcut = 1
|
|
1619
|
+
else:
|
|
1620
|
+
nrcombis = nradii*nradii*nradii
|
|
1621
|
+
_rcut = nradii
|
|
1622
|
+
NMM = np.zeros((3, self.nbinsz_lens*self.nbinsz_source*self.nbinsz_source, nrcombis), dtype=complex)
|
|
1623
|
+
tmprcombi = 0
|
|
1624
|
+
for elr1, R1 in enumerate(radii):
|
|
1625
|
+
for elr2, R2 in enumerate(radii[:_rcut]):
|
|
1626
|
+
for elr3, R3 in enumerate(radii[:_rcut]):
|
|
1627
|
+
if not do_multiscale:
|
|
1628
|
+
R2 = R1
|
|
1629
|
+
R3 = R1
|
|
1630
|
+
if filtercache is not None:
|
|
1631
|
+
A_NMM = filtercache[tmprcombi]
|
|
1632
|
+
else:
|
|
1633
|
+
A_NMM = self._NMM_filtergrid(R1, R2, R3)
|
|
1634
|
+
_NMM = np.nansum(A_NMM[0]*self.npcf[0,...],axis=(1,2,3))
|
|
1635
|
+
_NMMstar = np.nansum(A_NMM[1]*self.npcf[1,...],axis=(1,2,3))
|
|
1636
|
+
NMM[0,:,tmprcombi] = (_NMM + _NMMstar).real/2.
|
|
1637
|
+
NMM[1,:,tmprcombi] = (-_NMM + _NMMstar).real/2.
|
|
1638
|
+
NMM[2,:,tmprcombi] = (_NMM + _NMMstar).imag/2.
|
|
1639
|
+
tmprcombi += 1
|
|
1640
|
+
return NMM
|
|
1641
|
+
|
|
1642
|
+
def _NMM_filtergrid(self, R1, R2, R3):
|
|
1643
|
+
return self.__NMM_filtergrid(R1, R2, R3, self.bin_edges, self.bin_centers_mean, self.phi)
|
|
1644
|
+
|
|
1645
|
+
@staticmethod
|
|
1646
|
+
@jit(nopython=True, parallel=True)
|
|
1647
|
+
def __NMM_filtergrid(R1, R2, R3, edges, centers, phis):
|
|
1648
|
+
nbinsr = len(centers)
|
|
1649
|
+
nbinsphi = len(phis)
|
|
1650
|
+
_cphis = np.cos(phis)
|
|
1651
|
+
_ephis = np.e**(1J*phis)
|
|
1652
|
+
_ephisc = np.e**(-1J*phis)
|
|
1653
|
+
Theta4 = 1./3. * (R1**2*R2**2 + R1**2*R3**2 + R2**2*R3**2)
|
|
1654
|
+
a2 = 2./3. * R1**2*R2**2*R3**2 / Theta4
|
|
1655
|
+
ANMM = np.zeros((2,nbinsr,nbinsr,nbinsphi), dtype=nb_complex128)
|
|
1656
|
+
for elb in prange(nbinsr*nbinsr):
|
|
1657
|
+
elb1 = int(elb//nbinsr)
|
|
1658
|
+
elb2 = elb%nbinsr
|
|
1659
|
+
_y1 = centers[elb1]
|
|
1660
|
+
_dbin1 = edges[elb1+1] - edges[elb1]
|
|
1661
|
+
_y2 = centers[elb2]
|
|
1662
|
+
_dbin2 = edges[elb2+1] - edges[elb2]
|
|
1663
|
+
_dbinphi = phis[1] - phis[0]
|
|
1664
|
+
|
|
1665
|
+
csq = a2**2/4. * (_y1**2/R1**4 + _y2**2/R2**4 + 2*_y1*_y2*_cphis/(R1**2*R2**2))
|
|
1666
|
+
b0 = _y1**2/(2*R1**2)+_y2**2/(2*R2**2) - csq/a2
|
|
1667
|
+
|
|
1668
|
+
g1 = _y1 - a2/2. * (_y1/R1**2 + _y2*_ephisc/R2**2)
|
|
1669
|
+
g2 = _y2 - a2/2. * (_y2/R2**2 + _y1*_ephis/R1**2)
|
|
1670
|
+
g1c = g1.conj()
|
|
1671
|
+
g2c = g2.conj()
|
|
1672
|
+
pref = np.e**(-b0)/(72*np.pi*Theta4**2)
|
|
1673
|
+
_h1 = 2*(g2c*_y1+g1*_y2-2*g1*g2c)*(g1*g2c+2*a2*_ephisc)
|
|
1674
|
+
_h2 = 2*a2*(2*R3**2-csq-3*a2)*_ephisc*_ephisc
|
|
1675
|
+
_h3 = 4*g1*g2c*(2*R3**2-csq-2*a2)*_ephisc
|
|
1676
|
+
_h4 = (g1*g2c)**2/a2 * (2*R3**2-csq-a2)
|
|
1677
|
+
sum_MMN = pref*g1*g2 * ((R3**2/R1**2+R3**2/R2**2-csq/a2)*g1*g2 + 2*(g2*_y1+g1*_y2-2*g1*g2))
|
|
1678
|
+
sum_MMstarN = pref * (_h1 + _h2 + _h3 + _h4)
|
|
1679
|
+
_measures = _y1*_dbin1 * _y2*_dbin2 * _dbinphi
|
|
1680
|
+
|
|
1681
|
+
ANMM[0,elb1,elb2] = _measures * sum_MMN
|
|
1682
|
+
ANMM[1,elb1,elb2] = _measures * sum_MMstarN
|
|
1683
|
+
|
|
1684
|
+
return ANMM
|