orpheus-npcf 0.1.11__cp39-cp39-macosx_14_0_arm64.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.
Potentially problematic release.
This version of orpheus-npcf might be problematic. Click here for more details.
- orpheus/.dylibs/libgomp.1.dylib +0 -0
- orpheus/__init__.py +6 -0
- orpheus/catalog.py +1202 -0
- orpheus/covariance.py +153 -0
- orpheus/direct.py +1083 -0
- orpheus/flat2dgrid.py +44 -0
- orpheus/npcf.py +3696 -0
- orpheus/orpheus_clib.cpython-39-darwin.so +0 -0
- orpheus/patchutils.py +356 -0
- orpheus/utils.py +152 -0
- orpheus_npcf-0.1.11.dist-info/METADATA +67 -0
- orpheus_npcf-0.1.11.dist-info/RECORD +15 -0
- orpheus_npcf-0.1.11.dist-info/WHEEL +6 -0
- orpheus_npcf-0.1.11.dist-info/licenses/LICENSE +674 -0
- orpheus_npcf-0.1.11.dist-info/top_level.txt +1 -0
orpheus/npcf.py
ADDED
|
@@ -0,0 +1,3696 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
|
|
3
|
+
import ctypes as ct
|
|
4
|
+
from functools import reduce
|
|
5
|
+
import glob
|
|
6
|
+
from numba import jit, prange
|
|
7
|
+
from numba import config as nb_config
|
|
8
|
+
from numba import complex128 as nb_complex128
|
|
9
|
+
import numpy as np
|
|
10
|
+
from numpy.ctypeslib import ndpointer
|
|
11
|
+
import operator
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from scipy.interpolate import interp1d
|
|
14
|
+
import sys
|
|
15
|
+
from .catalog import Catalog, ScalarTracerCatalog, SpinTracerCatalog
|
|
16
|
+
from .utils import flatlist, gen_thetacombis_fourthorder, gen_n2n3indices_Upsfourth
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
__all__ = ["BinnedNPCF",
|
|
20
|
+
"GGCorrelation",
|
|
21
|
+
"GGGCorrelation", "GNNCorrelation", "NGGCorrelation",
|
|
22
|
+
"GGGGCorrelation_NoTomo", "NNNNCorrelation_NoTomo"]
|
|
23
|
+
|
|
24
|
+
################################################
|
|
25
|
+
## BASE CLASSES FOR NPCF AND THEIR MULTIPOLES ##
|
|
26
|
+
################################################
|
|
27
|
+
class BinnedNPCF:
|
|
28
|
+
r"""Class of an binned N-point correlation function of various arbitrary tracer catalogs.
|
|
29
|
+
This class contains attributes and metods that can be used across any its children.
|
|
30
|
+
|
|
31
|
+
Attributes
|
|
32
|
+
----------
|
|
33
|
+
order: int
|
|
34
|
+
The order of the correlation function.
|
|
35
|
+
spins: list
|
|
36
|
+
The spins of the tracer fields of which the NPCF is computed.
|
|
37
|
+
n_cfs: int
|
|
38
|
+
The number of independent components of the NPCF.
|
|
39
|
+
min_sep: float
|
|
40
|
+
The smallest distance of each vertex for which the NPCF is computed.
|
|
41
|
+
max_sep: float
|
|
42
|
+
The largest distance of each vertex for which the NPCF is computed.
|
|
43
|
+
nbinsr: int, optional
|
|
44
|
+
The number of radial bins for each vertex of the NPCF. If set to
|
|
45
|
+
``None`` this attribute is inferred from the ``binsize`` attribute.
|
|
46
|
+
binsize: int, optional
|
|
47
|
+
The logarithmic slize of the radial bins for each vertex of the NPCF. If set to
|
|
48
|
+
``None`` this attribute is inferred from the ``nbinsr`` attribute.
|
|
49
|
+
nbinsphi: float, optional
|
|
50
|
+
The number of angular bins for the NPCF in the real-space basis.
|
|
51
|
+
Defaults to ``100``.
|
|
52
|
+
nmaxs: list, optional
|
|
53
|
+
The largest multipole component considered for the NPCF in the multipole basis.
|
|
54
|
+
Defaults to ``30``.
|
|
55
|
+
method: str, optional
|
|
56
|
+
The method to be employed for the estimator. Defaults to ``DoubleTree``.
|
|
57
|
+
multicountcorr: bool, optional
|
|
58
|
+
Flag on whether to subtract of multiplets in which the same tracer appears more
|
|
59
|
+
than once. Defaults to ``True``.
|
|
60
|
+
shuffle_pix: int, optional
|
|
61
|
+
Choice of how to define centers of the cells in the spatial hash structure.
|
|
62
|
+
Defaults to ``1``, i.e. random positioning.
|
|
63
|
+
tree_resos: list, optional
|
|
64
|
+
The cell sizes of the hierarchical spatial hash structure
|
|
65
|
+
tree_redges: list, optional
|
|
66
|
+
List of radii where the tree changes resolution.
|
|
67
|
+
rmin_pixsize: int, optional
|
|
68
|
+
The limiting radial distance relative to the cell of the spatial hash
|
|
69
|
+
after which one switches to the next hash in the hierarchy. Defaults to ``20``.
|
|
70
|
+
resoshift_leafs: int, optional
|
|
71
|
+
Allows for a difference in how the hierarchical spatial hash is traversed for
|
|
72
|
+
pixels at the base of the NPCF and pixels at leafs. I.e. positive values indicate
|
|
73
|
+
that leafs will be evaluated at a courser resolutions than the base. Defaults to ``0``.
|
|
74
|
+
minresoind_leaf: int, optional
|
|
75
|
+
Sets the smallest resolution in the spatial hash hierarchy which can be used to access
|
|
76
|
+
tracers at leaf positions. If set to ``None`` uses the smallest specified cell size.
|
|
77
|
+
Defaults to ``None``.
|
|
78
|
+
maxresoind_leaf: int, optional
|
|
79
|
+
Sets the largest resolution in the spatial hash hierarchy which can be used to access
|
|
80
|
+
tracers at leaf positions. If set to ``None`` uses the largest specified cell size.
|
|
81
|
+
Defaults to ``None``.
|
|
82
|
+
verbosity: int, optional
|
|
83
|
+
The level of verbosity during the computation. Level 0: No verbosity, 1: Progress verbosity
|
|
84
|
+
on python layer, 2: Progress verbosity also on C level, 3: Debug verbosity. Defaults to ``0``.
|
|
85
|
+
nthreads: int, optional
|
|
86
|
+
The number of openmp threads used for the reduction procedure. Defaults to ``16``.
|
|
87
|
+
bin_centers: numpy.ndarray
|
|
88
|
+
The centers of the radial bins for each combination of tomographic redshifts.
|
|
89
|
+
bin_centers_mean: numpy.ndarray
|
|
90
|
+
The centers of the radial bins averaged over all combination of tomographic redshifts.
|
|
91
|
+
phis: list
|
|
92
|
+
The bin centers for the N-2 angles describing the NPCF
|
|
93
|
+
in the real-space basis.
|
|
94
|
+
npcf: numpy.ndarray
|
|
95
|
+
The natural components of the NPCF in the real space basis. The different axes
|
|
96
|
+
are specified as follows: ``(component, zcombi, rbin_1, ..., rbin_N-1, phiin_1, phibin_N-2)``.
|
|
97
|
+
npcf_norm: numpy.ndarray
|
|
98
|
+
The normalization of the natural components of the NPCF in the real space basis. The different axes
|
|
99
|
+
are specified as follows: ``(zcombi, rbin_1, ..., rbin_N-1, phiin_1, phibin_N-2)``.
|
|
100
|
+
npcf_multipoles: numpy.ndarray
|
|
101
|
+
The natural components of the NPCF in the multipole basis. The different axes
|
|
102
|
+
are specified as follows: ``(component, zcombi, multipole_1, ..., multipole_N-2, rbin_1, ..., rbin_N-1)``.
|
|
103
|
+
npcf_multipoles_norm: numpy.ndarray
|
|
104
|
+
The normalization of the natural components of the NPCF in the multipole basis. The different axes
|
|
105
|
+
are specified as follows: ``(zcombi, multipole_1, ..., multipole_N-2, rbin_1, ..., rbin_N-1)``.
|
|
106
|
+
is_edge_corrected: bool, optional
|
|
107
|
+
Flag signifying on wheter the NPCF multipoles have beed edge-corrected. Defaults to ``False``.
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
def __init__(self, order, spins, n_cfs, min_sep, max_sep, nbinsr=None, binsize=None, nbinsphi=100,
|
|
111
|
+
nmaxs=30, method="DoubleTree", multicountcorr=True, shuffle_pix=0,
|
|
112
|
+
tree_resos=[0,0.25,0.5,1.,2.], tree_redges=None, rmin_pixsize=20,
|
|
113
|
+
resoshift_leafs=0, minresoind_leaf=None, maxresoind_leaf=None,
|
|
114
|
+
methods_avail=["Discrete", "Tree", "BaseTree", "DoubleTree"], verbosity=0, nthreads=16):
|
|
115
|
+
|
|
116
|
+
self.order = int(order)
|
|
117
|
+
self.n_cfs = int(n_cfs)
|
|
118
|
+
self.min_sep = min_sep
|
|
119
|
+
self.max_sep = max_sep
|
|
120
|
+
self.nbinsphi = nbinsphi
|
|
121
|
+
self.nmaxs = nmaxs
|
|
122
|
+
self.method = method
|
|
123
|
+
self.multicountcorr = int(multicountcorr)
|
|
124
|
+
self.shuffle_pix = shuffle_pix
|
|
125
|
+
self.methods_avail = methods_avail
|
|
126
|
+
self.tree_resos = np.asarray(tree_resos, dtype=np.float64)
|
|
127
|
+
self.tree_nresos = int(len(self.tree_resos))
|
|
128
|
+
self.tree_redges = tree_redges
|
|
129
|
+
self.rmin_pixsize = rmin_pixsize
|
|
130
|
+
self.resoshift_leafs = resoshift_leafs
|
|
131
|
+
self.minresoind_leaf = minresoind_leaf
|
|
132
|
+
self.maxresoind_leaf = maxresoind_leaf
|
|
133
|
+
self.verbosity = np.int32(verbosity)
|
|
134
|
+
self.nthreads = np.int32(max(1,nthreads))
|
|
135
|
+
|
|
136
|
+
self.tree_resosatr = None
|
|
137
|
+
self.bin_centers = None
|
|
138
|
+
self.bin_centers_mean = None
|
|
139
|
+
self.phis = [None]*self.order
|
|
140
|
+
self.dphis = [None]*self.order
|
|
141
|
+
self.npcf = None
|
|
142
|
+
self.npcf_norm = None
|
|
143
|
+
self.npcf_multipoles = None
|
|
144
|
+
self.npcf_multipoles_norm = None
|
|
145
|
+
self.is_edge_corrected = False
|
|
146
|
+
self._verbose_python = self.verbosity > 0
|
|
147
|
+
self._verbose_c = self.verbosity > 1
|
|
148
|
+
self._verbose_debug = self.verbosity > 2
|
|
149
|
+
|
|
150
|
+
# Check types or arguments
|
|
151
|
+
if isinstance(self.nbinsphi, int):
|
|
152
|
+
self.nbinsphi = self.nbinsphi*np.ones(order-2)
|
|
153
|
+
self.nbinsphi = self.nbinsphi.astype(np.int32)
|
|
154
|
+
if isinstance(self.nmaxs, int):
|
|
155
|
+
self.nmaxs = self.nmaxs*np.ones(order-2)
|
|
156
|
+
self.nmaxs = self.nmaxs.astype(np.int32)
|
|
157
|
+
if isinstance(spins, int):
|
|
158
|
+
spins = spins*np.ones(order).astype(np.int32)
|
|
159
|
+
self.spins = np.asarray(spins, dtype=np.int32)
|
|
160
|
+
assert(isinstance(self.order, int))
|
|
161
|
+
assert(isinstance(self.spins, np.ndarray))
|
|
162
|
+
assert(isinstance(self.spins[0], np.int32))
|
|
163
|
+
assert(len(spins)==self.order)
|
|
164
|
+
assert(isinstance(self.n_cfs, int))
|
|
165
|
+
assert(isinstance(self.min_sep, float))
|
|
166
|
+
assert(isinstance(self.max_sep, float))
|
|
167
|
+
if self.order>2:
|
|
168
|
+
assert(isinstance(self.nbinsphi, np.ndarray))
|
|
169
|
+
assert(isinstance(self.nbinsphi[0], np.int32))
|
|
170
|
+
assert(len(self.nbinsphi)==self.order-2)
|
|
171
|
+
assert(isinstance(self.nmaxs, np.ndarray))
|
|
172
|
+
assert(isinstance(self.nmaxs[0], np.int32))
|
|
173
|
+
assert(len(self.nmaxs)==self.order-2)
|
|
174
|
+
assert(self.method in self.methods_avail)
|
|
175
|
+
assert(isinstance(self.tree_resos, np.ndarray))
|
|
176
|
+
assert(isinstance(self.tree_resos[0], np.float64))
|
|
177
|
+
|
|
178
|
+
# Setup radial bins
|
|
179
|
+
# Note that we always have self.binsize <= binsize
|
|
180
|
+
assert((binsize!=None) or (nbinsr!=None))
|
|
181
|
+
if nbinsr != None:
|
|
182
|
+
self.nbinsr = int(nbinsr)
|
|
183
|
+
if binsize != None:
|
|
184
|
+
assert(isinstance(binsize, float))
|
|
185
|
+
self.nbinsr = int(np.ceil(np.log(self.max_sep/self.min_sep)/binsize))
|
|
186
|
+
assert(isinstance(self.nbinsr, int))
|
|
187
|
+
self.bin_edges = np.geomspace(self.min_sep, self.max_sep, self.nbinsr+1)
|
|
188
|
+
self.binsize = np.log(self.bin_edges[1]/self.bin_edges[0])
|
|
189
|
+
# Setup variable for tree estimator according to input
|
|
190
|
+
if self.tree_redges != None:
|
|
191
|
+
assert(isinstance(self.tree_redges, np.ndarray))
|
|
192
|
+
self.tree_redges = self.tree_redges.astype(np.float64)
|
|
193
|
+
assert(len(self.tree_redges)==self.tree_resos+1)
|
|
194
|
+
self.tree_redges = np.sort(self.tree_redges)
|
|
195
|
+
assert(self.tree_redges[0]==self.min_sep)
|
|
196
|
+
assert(self.tree_redges[-1]==self.max_sep)
|
|
197
|
+
else:
|
|
198
|
+
self.tree_redges = np.zeros(len(self.tree_resos)+1)
|
|
199
|
+
self.tree_redges[-1] = self.max_sep
|
|
200
|
+
for elreso, reso in enumerate(self.tree_resos):
|
|
201
|
+
self.tree_redges[elreso] = (reso==0.)*self.min_sep + (reso!=0.)*self.rmin_pixsize*reso
|
|
202
|
+
_tmpreso = 0
|
|
203
|
+
self.tree_resosatr = np.zeros(self.nbinsr, dtype=np.int32)
|
|
204
|
+
for elbin, rbin in enumerate(self.bin_edges[:-1]):
|
|
205
|
+
if rbin > self.tree_redges[_tmpreso+1]:
|
|
206
|
+
_tmpreso += 1
|
|
207
|
+
self.tree_resosatr[elbin] = _tmpreso
|
|
208
|
+
# Update tree resolutions to make sure that `tree_redges` is monotonous
|
|
209
|
+
# (This is i.e. not fulfilled for a default tree setup and a large value of `rmin`)
|
|
210
|
+
_resomin = self.tree_resosatr[0]
|
|
211
|
+
_resomax = self.tree_resosatr[-1]
|
|
212
|
+
self._updatetree(self.tree_resos[_resomin:_resomax+1])
|
|
213
|
+
|
|
214
|
+
# Prepare leaf resolutions
|
|
215
|
+
if np.abs(self.resoshift_leafs)>=self.tree_nresos:
|
|
216
|
+
self.resoshift_leafs = np.int32((self.tree_nresos-1) * np.sign(self.resoshift_leafs))
|
|
217
|
+
print("Error: Parameter resoshift_leafs is out of bounds. Set to %i."%self.resoshift_leafs)
|
|
218
|
+
if self.minresoind_leaf is None:
|
|
219
|
+
self.minresoind_leaf=0
|
|
220
|
+
if self.maxresoind_leaf is None:
|
|
221
|
+
self.maxresoind_leaf=self.tree_nresos-1
|
|
222
|
+
if self.minresoind_leaf<0:
|
|
223
|
+
self.minresoind_leaf = 0
|
|
224
|
+
print("Error: Parameter minreso_leaf is out of bounds. Set to 0.")
|
|
225
|
+
if self.minresoind_leaf>=self.tree_nresos:
|
|
226
|
+
self.minresoind_leaf = self.tree_nresos-1
|
|
227
|
+
print("Error: Parameter minreso_leaf is out of bounds. Set to %i."%self.minresoint_leaf)
|
|
228
|
+
if self.maxresoind_leaf<0:
|
|
229
|
+
self.maxresoind_leaf = 0
|
|
230
|
+
print("Error: Parameter minreso_leaf is out of bounds. Set to 0.")
|
|
231
|
+
if self.maxresoind_leaf>=self.tree_nresos:
|
|
232
|
+
self.maxresoind_leaf = self.tree_nresos-1
|
|
233
|
+
print("Error: Parameter minreso_leaf is out of bounds. Set to %i."%self.maxresoint_leaf)
|
|
234
|
+
if self.maxresoind_leaf<self.minresoind_leaf:
|
|
235
|
+
print("Error: Parameter maxreso_leaf is smaller than minreso_leaf. Set to %i."%self.minreso_leaf)
|
|
236
|
+
|
|
237
|
+
# Setup phi bins
|
|
238
|
+
for elp in range(self.order-2):
|
|
239
|
+
_ = np.linspace(0,2*np.pi,self.nbinsphi[elp]+1)
|
|
240
|
+
self.phis[elp] = .5*(_[1:] + _[:-1])
|
|
241
|
+
self.dphis[elp] = _[1:] - _[:-1]
|
|
242
|
+
|
|
243
|
+
#############################
|
|
244
|
+
## Link compiled libraries ##
|
|
245
|
+
#############################
|
|
246
|
+
# Method that works for LP
|
|
247
|
+
target_path = __import__('orpheus').__file__
|
|
248
|
+
self.library_path = str(Path(__import__('orpheus').__file__).parent.absolute())
|
|
249
|
+
self.clib = ct.CDLL(glob.glob(self.library_path+"/orpheus_clib*.so")[0])
|
|
250
|
+
|
|
251
|
+
# In case the environment is weird, compile code manually and load it here...
|
|
252
|
+
#self.clib = ct.CDLL("/vol/euclidraid4/data/lporth/HigherOrderLensing/Estimator/orpheus/orpheus/src/discrete.so")
|
|
253
|
+
|
|
254
|
+
# Method that works for RR (but not for LP with a local HPC install)
|
|
255
|
+
#self.clib = ct.CDLL(search_file_in_site_package(get_site_packages_dir(),"orpheus_clib"))
|
|
256
|
+
#self.library_path = str(Path(__import__('orpheus').__file__).parent.parent.absolute())
|
|
257
|
+
#print(self.library_path)
|
|
258
|
+
#print(self.clib)
|
|
259
|
+
p_c128 = ndpointer(complex, flags="C_CONTIGUOUS")
|
|
260
|
+
p_f64 = ndpointer(np.float64, flags="C_CONTIGUOUS")
|
|
261
|
+
p_f32 = ndpointer(np.float32, flags="C_CONTIGUOUS")
|
|
262
|
+
p_i32 = ndpointer(np.int32, flags="C_CONTIGUOUS")
|
|
263
|
+
p_f64_nof = ndpointer(np.float64)
|
|
264
|
+
|
|
265
|
+
## Second order scalar-scalar statistics ##
|
|
266
|
+
if self.order==2 and np.array_equal(self.spins, np.array([0, 0], dtype=np.int32)):
|
|
267
|
+
self.clib.alloc_NNcounts_doubletree.restype = ct.c_void_p
|
|
268
|
+
self.clib.alloc_NNcounts_doubletree.argtypes = [
|
|
269
|
+
ct.c_int32, ct.c_int32, p_f64, p_f64, p_f64,
|
|
270
|
+
ct.c_int32, ct.c_int32, ct.c_int32,
|
|
271
|
+
p_i32, ct.c_int32, p_f64, p_f64, p_f64, p_f64, p_f64, p_i32,
|
|
272
|
+
p_i32, p_i32, p_i32,
|
|
273
|
+
ct.c_double, ct.c_double, ct.c_int32, ct.c_double, ct.c_double, ct.c_int32, ct.c_int32, p_i32,
|
|
274
|
+
ct.c_double, ct.c_double, ct.c_int32, ct.c_int32, ct.c_int32,
|
|
275
|
+
np.ctypeslib.ndpointer(dtype=np.float64),
|
|
276
|
+
np.ctypeslib.ndpointer(dtype=np.float64),
|
|
277
|
+
np.ctypeslib.ndpointer(dtype=np.int64)]
|
|
278
|
+
|
|
279
|
+
## Second order shear-shear statistics ##
|
|
280
|
+
if self.order==2 and np.array_equal(self.spins, np.array([2, 2], dtype=np.int32)):
|
|
281
|
+
# Doubletree-based estimator of second-order shear correlation function
|
|
282
|
+
self.clib.alloc_xipm_doubletree.restype = ct.c_void_p
|
|
283
|
+
self.clib.alloc_xipm_doubletree.argtypes = [
|
|
284
|
+
ct.c_int32, ct.c_int32, p_f64, p_f64, p_f64,
|
|
285
|
+
ct.c_int32, ct.c_int32, ct.c_int32,
|
|
286
|
+
p_i32, ct.c_int32, p_f64, p_f64, p_f64, p_f64, p_f64, p_f64, p_i32,
|
|
287
|
+
p_i32, p_i32, p_i32,
|
|
288
|
+
ct.c_double, ct.c_double, ct.c_int32, ct.c_double, ct.c_double, ct.c_int32, ct.c_int32, p_i32,
|
|
289
|
+
ct.c_double, ct.c_double, ct.c_int32, ct.c_int32, ct.c_int32, ct.c_int32,
|
|
290
|
+
np.ctypeslib.ndpointer(dtype=np.float64),
|
|
291
|
+
np.ctypeslib.ndpointer(dtype=np.complex128),
|
|
292
|
+
np.ctypeslib.ndpointer(dtype=np.complex128),
|
|
293
|
+
np.ctypeslib.ndpointer(dtype=np.float64),
|
|
294
|
+
np.ctypeslib.ndpointer(dtype=np.int64)]
|
|
295
|
+
|
|
296
|
+
## Third order shear-shear-shear statistics ##
|
|
297
|
+
if self.order==3 and np.array_equal(self.spins, np.array([2, 2, 2], dtype=np.int32)):
|
|
298
|
+
# Discrete estimator of third-order shear correlation function
|
|
299
|
+
self.clib.alloc_Gammans_discrete_ggg.restype = ct.c_void_p
|
|
300
|
+
self.clib.alloc_Gammans_discrete_ggg.argtypes = [
|
|
301
|
+
p_f64, p_f64, p_f64, p_f64, p_f64, p_f64, p_i32, ct.c_int32, ct.c_int32,
|
|
302
|
+
ct.c_int32, ct.c_int32, ct.c_double, ct.c_double, p_f64, ct.c_int32, ct.c_int32,
|
|
303
|
+
p_i32, p_i32, p_i32, ct.c_double, ct.c_double, ct.c_int32,
|
|
304
|
+
ct.c_double, ct.c_double, ct.c_int32, ct.c_int32, ct.c_int32,
|
|
305
|
+
np.ctypeslib.ndpointer(dtype=np.float64),
|
|
306
|
+
np.ctypeslib.ndpointer(dtype=np.complex128),
|
|
307
|
+
np.ctypeslib.ndpointer(dtype=np.complex128)]
|
|
308
|
+
|
|
309
|
+
# Tree-based estimator of third-order shear correlation function
|
|
310
|
+
self.clib.alloc_Gammans_tree_ggg.restype = ct.c_void_p
|
|
311
|
+
self.clib.alloc_Gammans_tree_ggg.argtypes = [
|
|
312
|
+
p_f64, p_f64, p_f64, p_f64, p_f64, p_f64, p_i32, ct.c_int32, ct.c_int32,
|
|
313
|
+
ct.c_int32, p_f64, p_i32,
|
|
314
|
+
p_f64, p_f64, p_f64, p_f64, p_f64, p_i32, p_f64,
|
|
315
|
+
p_i32, p_i32, p_i32, ct.c_double, ct.c_double, ct.c_int32, ct.c_double, ct.c_double, ct.c_int32,
|
|
316
|
+
ct.c_int32, ct.c_int32, ct.c_double, ct.c_double, p_f64, ct.c_int32, ct.c_int32, ct.c_int32, ct.c_int32,
|
|
317
|
+
np.ctypeslib.ndpointer(dtype=np.float64),
|
|
318
|
+
np.ctypeslib.ndpointer(dtype=np.complex128),
|
|
319
|
+
np.ctypeslib.ndpointer(dtype=np.complex128)]
|
|
320
|
+
|
|
321
|
+
# Basetree-based estimator of third-order shear correlation function
|
|
322
|
+
self.clib.alloc_Gammans_basetree_ggg.restype = ct.c_void_p
|
|
323
|
+
self.clib.alloc_Gammans_basetree_ggg.argtypes = [
|
|
324
|
+
ct.c_int32, ct.c_int32, p_f64, p_f64, p_f64, p_i32, ct.c_int32,
|
|
325
|
+
p_f64, p_f64, p_f64, p_f64, p_f64, p_f64, p_i32, p_f64,
|
|
326
|
+
p_i32, p_i32, p_i32,
|
|
327
|
+
ct.c_double, ct.c_double, ct.c_int32, ct.c_double, ct.c_double, ct.c_int32,
|
|
328
|
+
p_i32, ct.c_int32, p_i32, ct.c_int32,
|
|
329
|
+
ct.c_int32, ct.c_double, ct.c_double, ct.c_int32, ct.c_int32, ct.c_int32, ct.c_int32,
|
|
330
|
+
np.ctypeslib.ndpointer(dtype=np.float64),
|
|
331
|
+
np.ctypeslib.ndpointer(dtype=np.complex128),
|
|
332
|
+
np.ctypeslib.ndpointer(dtype=np.complex128)]
|
|
333
|
+
|
|
334
|
+
# Doubletree-based estimator of third-order shear correlation function
|
|
335
|
+
self.clib.alloc_Gammans_doubletree_ggg.restype = ct.c_void_p
|
|
336
|
+
self.clib.alloc_Gammans_doubletree_ggg.argtypes = [
|
|
337
|
+
ct.c_int32, ct.c_int32, p_f64, p_f64, p_f64,
|
|
338
|
+
ct.c_int32, ct.c_int32, ct.c_int32,
|
|
339
|
+
p_i32, ct.c_int32, p_f64, p_f64, p_f64, p_f64, p_f64, p_f64, p_i32, p_f64,
|
|
340
|
+
p_i32, p_i32, p_i32,
|
|
341
|
+
ct.c_double, ct.c_double, ct.c_int32, ct.c_double, ct.c_double, ct.c_int32,
|
|
342
|
+
p_i32, ct.c_int32, p_i32, ct.c_int32,
|
|
343
|
+
ct.c_int32, ct.c_double, ct.c_double, ct.c_int32, ct.c_int32, ct.c_int32, ct.c_int32,
|
|
344
|
+
np.ctypeslib.ndpointer(dtype=np.float64),
|
|
345
|
+
np.ctypeslib.ndpointer(dtype=np.complex128),
|
|
346
|
+
np.ctypeslib.ndpointer(dtype=np.complex128)]
|
|
347
|
+
|
|
348
|
+
# Conversion between 3pcf multipoles and 3pcf
|
|
349
|
+
self.clib.multipoles2npcf_ggg.restype = ct.c_void_p
|
|
350
|
+
self.clib.multipoles2npcf_ggg.argtypes = [
|
|
351
|
+
p_c128, p_c128, ct.c_int32, ct.c_int32,
|
|
352
|
+
p_f64, ct.c_int32, p_f64, ct.c_int32, ct.c_int32,
|
|
353
|
+
ct.c_int32,
|
|
354
|
+
np.ctypeslib.ndpointer(dtype=np.complex128),
|
|
355
|
+
np.ctypeslib.ndpointer(dtype=np.complex128)]
|
|
356
|
+
|
|
357
|
+
# Change projection of 3pcf between x and centroid
|
|
358
|
+
self.clib._x2centroid_ggg.restype = ct.c_void_p
|
|
359
|
+
self.clib._x2centroid_ggg.argtypes = [
|
|
360
|
+
p_c128, ct.c_int32,
|
|
361
|
+
p_f64, ct.c_int32, p_f64, ct.c_int32, ct.c_int32]
|
|
362
|
+
|
|
363
|
+
## Third-order source-lens-lens statistics ##
|
|
364
|
+
if self.order==3 and np.array_equal(self.spins, np.array([2, 0, 0], dtype=np.int32)):
|
|
365
|
+
# Discrete estimator of third-order source-lens-lens (G3L) correlation function
|
|
366
|
+
self.clib.alloc_Gammans_discrete_GNN.restype = ct.c_void_p
|
|
367
|
+
self.clib.alloc_Gammans_discrete_GNN.argtypes = [
|
|
368
|
+
p_f64, p_f64, p_f64, p_f64, p_f64, p_f64, p_i32, ct.c_int32, ct.c_int32,
|
|
369
|
+
p_f64, p_f64, p_f64, p_i32, ct.c_int32, ct.c_int32,
|
|
370
|
+
ct.c_int32, ct.c_double, ct.c_double, ct.c_int32, ct.c_int32,
|
|
371
|
+
p_i32, p_i32, p_i32, p_i32, p_i32, p_i32, ct.c_int32,
|
|
372
|
+
ct.c_double, ct.c_double, ct.c_int32, ct.c_double, ct.c_double, ct.c_int32, ct.c_int32, ct.c_int32,
|
|
373
|
+
np.ctypeslib.ndpointer(dtype=np.float64),
|
|
374
|
+
np.ctypeslib.ndpointer(dtype=np.complex128),
|
|
375
|
+
np.ctypeslib.ndpointer(dtype=np.complex128)]
|
|
376
|
+
|
|
377
|
+
# Doubletree-based estimator of third-order source-lens-lens (G3L) correlation function
|
|
378
|
+
self.clib.alloc_Gammans_doubletree_GNN.restype = ct.c_void_p
|
|
379
|
+
self.clib.alloc_Gammans_doubletree_GNN.argtypes = [
|
|
380
|
+
ct.c_int32, ct.c_int32, p_f64, p_f64, p_f64,
|
|
381
|
+
ct.c_int32, ct.c_int32, ct.c_int32,
|
|
382
|
+
p_f64, p_f64, p_f64, p_f64, p_f64, p_f64, p_i32, p_i32, ct.c_int32,
|
|
383
|
+
p_f64, p_f64, p_f64, p_f64, p_i32, p_i32, ct.c_int32,
|
|
384
|
+
ct.c_int32, ct.c_double, ct.c_double, ct.c_int32, ct.c_int32,
|
|
385
|
+
p_i32, p_i32, p_i32, p_i32, p_i32, p_i32, p_i32, ct.c_int32,
|
|
386
|
+
ct.c_double, ct.c_double, ct.c_int32, ct.c_double, ct.c_double, ct.c_int32, ct.c_int32, ct.c_int32,
|
|
387
|
+
np.ctypeslib.ndpointer(dtype=np.float64),
|
|
388
|
+
np.ctypeslib.ndpointer(dtype=np.complex128),
|
|
389
|
+
np.ctypeslib.ndpointer(dtype=np.complex128)]
|
|
390
|
+
|
|
391
|
+
## Third-order lens-source-source statistics ##
|
|
392
|
+
if self.order==3 and np.array_equal(self.spins, np.array([0, 2, 2], dtype=np.int32)):
|
|
393
|
+
# Discrete estimator of third-order lens-source-source correlation function
|
|
394
|
+
self.clib.alloc_Gammans_discrete_NGG.restype = ct.c_void_p
|
|
395
|
+
self.clib.alloc_Gammans_discrete_NGG.argtypes = [
|
|
396
|
+
p_f64, p_f64, p_f64, p_f64, p_f64, p_i32, ct.c_int32, ct.c_int32,
|
|
397
|
+
p_f64, p_f64, p_f64, p_f64, p_i32, ct.c_int32, ct.c_int32,
|
|
398
|
+
ct.c_int32, ct.c_double, ct.c_double, ct.c_int32, ct.c_int32,
|
|
399
|
+
p_i32, p_i32, p_i32, p_i32, p_i32, p_i32, ct.c_int32,
|
|
400
|
+
ct.c_double, ct.c_double, ct.c_int32, ct.c_double, ct.c_double, ct.c_int32, ct.c_int32, ct.c_int32,
|
|
401
|
+
np.ctypeslib.ndpointer(dtype=np.float64),
|
|
402
|
+
np.ctypeslib.ndpointer(dtype=np.complex128),
|
|
403
|
+
np.ctypeslib.ndpointer(dtype=np.complex128)]
|
|
404
|
+
|
|
405
|
+
self.clib.alloc_Gammans_tree_NGG.restype = ct.c_void_p
|
|
406
|
+
self.clib.alloc_Gammans_tree_NGG.argtypes = [
|
|
407
|
+
ct.c_int32, p_f64,
|
|
408
|
+
p_f64, p_f64, p_f64, p_f64, p_f64, p_i32, ct.c_int32, p_i32,
|
|
409
|
+
p_f64, p_f64, p_f64, p_f64, p_i32, ct.c_int32, ct.c_int32,
|
|
410
|
+
p_i32, p_i32, p_i32, p_i32, p_i32, p_i32, ct.c_int32,
|
|
411
|
+
ct.c_double, ct.c_double, ct.c_int32, ct.c_double, ct.c_double, ct.c_int32,
|
|
412
|
+
ct.c_int32, ct.c_double, ct.c_double, ct.c_int32, ct.c_int32, ct.c_int32, ct.c_int32,
|
|
413
|
+
np.ctypeslib.ndpointer(dtype=np.float64),
|
|
414
|
+
np.ctypeslib.ndpointer(dtype=np.complex128),
|
|
415
|
+
np.ctypeslib.ndpointer(dtype=np.complex128)]
|
|
416
|
+
|
|
417
|
+
self.clib.alloc_Gammans_doubletree_NGG.restype = ct.c_void_p
|
|
418
|
+
self.clib.alloc_Gammans_doubletree_NGG.argtypes = [
|
|
419
|
+
ct.c_int32, ct.c_int32, p_f64, p_f64, p_f64,
|
|
420
|
+
ct.c_int32, ct.c_int32, ct.c_int32,
|
|
421
|
+
p_f64, p_f64, p_f64, p_f64, p_f64, p_f64, p_i32, p_i32, ct.c_int32,
|
|
422
|
+
p_f64, p_f64, p_f64, p_f64, p_i32, p_i32, ct.c_int32,
|
|
423
|
+
ct.c_int32, ct.c_double, ct.c_double, ct.c_int32, ct.c_int32,
|
|
424
|
+
p_i32, p_i32, p_i32, p_i32, p_i32, p_i32, p_i32, ct.c_int32,
|
|
425
|
+
ct.c_double, ct.c_double, ct.c_int32, ct.c_double, ct.c_double, ct.c_int32, ct.c_int32, ct.c_int32,
|
|
426
|
+
np.ctypeslib.ndpointer(dtype=np.float64),
|
|
427
|
+
np.ctypeslib.ndpointer(dtype=np.complex128),
|
|
428
|
+
np.ctypeslib.ndpointer(dtype=np.complex128)]
|
|
429
|
+
|
|
430
|
+
## Fourth-order counts-counts-counts-counts statistics ##
|
|
431
|
+
if self.order==4 and np.array_equal(self.spins, np.array([0, 0, 0, 0], dtype=np.int32)):
|
|
432
|
+
|
|
433
|
+
# Tree estimator of non-tomographic fourth-order counts correlation function
|
|
434
|
+
self.clib.alloc_notomoGammans_tree_nnnn.restype = ct.c_void_p
|
|
435
|
+
self.clib.alloc_notomoGammans_tree_nnnn.argtypes = [
|
|
436
|
+
p_f64, p_f64, p_f64, p_f64, ct.c_int32,
|
|
437
|
+
ct.c_int32, ct.c_double, ct.c_double, ct.c_int32, ct.c_int32, ct.c_int32,
|
|
438
|
+
p_i32, ct.c_int32,
|
|
439
|
+
ct.c_int32, p_f64, p_i32,
|
|
440
|
+
p_f64, p_f64, p_f64, p_f64,
|
|
441
|
+
p_i32, p_i32, p_i32, ct.c_int32,
|
|
442
|
+
ct.c_double, ct.c_double, ct.c_int32, ct.c_double, ct.c_double, ct.c_int32, ct.c_int32, ct.c_int32,
|
|
443
|
+
np.ctypeslib.ndpointer(dtype=np.float64),
|
|
444
|
+
np.ctypeslib.ndpointer(dtype=np.complex128)]
|
|
445
|
+
|
|
446
|
+
# Tree-based estimator of non-tomographic Map^4 statistics (low-mem)
|
|
447
|
+
self.clib.alloc_notomoNap4_tree_nnnn.restype = ct.c_void_p
|
|
448
|
+
self.clib.alloc_notomoNap4_tree_nnnn.argtypes = [
|
|
449
|
+
p_f64, p_f64, p_f64, p_f64, ct.c_int32,
|
|
450
|
+
ct.c_int32, ct.c_double, ct.c_double, ct.c_int32, ct.c_int32,
|
|
451
|
+
p_i32, ct.c_int32, p_f64, p_f64, ct.c_int32,
|
|
452
|
+
ct.c_int32, p_f64, p_i32,
|
|
453
|
+
p_f64, p_f64, p_f64, p_f64,
|
|
454
|
+
p_i32, p_i32, p_i32, ct.c_int32,
|
|
455
|
+
ct.c_double, ct.c_double, ct.c_int32, ct.c_double, ct.c_double, ct.c_int32,
|
|
456
|
+
p_i32, p_i32, p_i32, ct.c_int32,
|
|
457
|
+
ct.c_int32, p_f64, ct.c_int32, np.ctypeslib.ndpointer(dtype=np.complex128),
|
|
458
|
+
ct.c_int32, ct.c_int32,
|
|
459
|
+
np.ctypeslib.ndpointer(dtype=np.float64),
|
|
460
|
+
np.ctypeslib.ndpointer(dtype=np.complex128),np.ctypeslib.ndpointer(dtype=np.complex128)]
|
|
461
|
+
|
|
462
|
+
# Transformation between 4PCF from multipole-basis tp real-space basis for a fixed
|
|
463
|
+
# combination of radial bins
|
|
464
|
+
self.clib.multipoles2npcf_nnnn_singletheta.restype = ct.c_void_p
|
|
465
|
+
self.clib.multipoles2npcf_nnnn_singletheta.argtypes = [
|
|
466
|
+
p_c128, ct.c_int32, ct.c_int32,
|
|
467
|
+
ct.c_double, ct.c_double, ct.c_double,
|
|
468
|
+
p_f64, p_f64, ct.c_int32, ct.c_int32,
|
|
469
|
+
np.ctypeslib.ndpointer(dtype=np.complex128)]
|
|
470
|
+
|
|
471
|
+
## Fourth-order shear-shear-shear-shear statistics ##
|
|
472
|
+
if self.order==4 and np.array_equal(self.spins, np.array([2, 2, 2, 2], dtype=np.int32)):
|
|
473
|
+
|
|
474
|
+
# Discrete estimator of non-tomographic fourth-order shear correlation function
|
|
475
|
+
self.clib.alloc_notomoGammans_discrete_gggg.restype = ct.c_void_p
|
|
476
|
+
self.clib.alloc_notomoGammans_discrete_gggg.argtypes = [
|
|
477
|
+
p_f64, p_f64, p_f64, p_f64, p_f64, p_f64, ct.c_int32,
|
|
478
|
+
ct.c_int32, ct.c_double, ct.c_double, p_f64, ct.c_int32, ct.c_int32,
|
|
479
|
+
p_i32, p_i32, p_i32, ct.c_int32,
|
|
480
|
+
ct.c_double, ct.c_double, ct.c_int32, ct.c_double, ct.c_double, ct.c_int32, ct.c_int32, ct.c_int32,
|
|
481
|
+
np.ctypeslib.ndpointer(dtype=np.float64),
|
|
482
|
+
np.ctypeslib.ndpointer(dtype=np.complex128),
|
|
483
|
+
np.ctypeslib.ndpointer(dtype=np.complex128)]
|
|
484
|
+
|
|
485
|
+
# Tree estimator of non-tomographic fourth-order shear correlation function
|
|
486
|
+
self.clib.alloc_notomoGammans_tree_gggg.restype = ct.c_void_p
|
|
487
|
+
self.clib.alloc_notomoGammans_tree_gggg.argtypes = [
|
|
488
|
+
p_f64, p_f64, p_f64, p_f64, p_f64, p_f64, ct.c_int32,
|
|
489
|
+
ct.c_int32, ct.c_double, ct.c_double, ct.c_int32, ct.c_int32, ct.c_int32,
|
|
490
|
+
p_i32, ct.c_int32,
|
|
491
|
+
ct.c_int32, p_f64, p_i32,
|
|
492
|
+
p_f64, p_f64, p_f64, p_f64, p_f64, p_f64,
|
|
493
|
+
p_i32, p_i32, p_i32, ct.c_int32,
|
|
494
|
+
ct.c_double, ct.c_double, ct.c_int32, ct.c_double, ct.c_double, ct.c_int32, ct.c_int32, ct.c_int32,
|
|
495
|
+
np.ctypeslib.ndpointer(dtype=np.float64),
|
|
496
|
+
np.ctypeslib.ndpointer(dtype=np.complex128),
|
|
497
|
+
np.ctypeslib.ndpointer(dtype=np.complex128)]
|
|
498
|
+
|
|
499
|
+
# Discrete estimator of non-tomographic Map^4 statistics (low-mem)
|
|
500
|
+
self.clib.alloc_notomoMap4_disc_gggg.restype = ct.c_void_p
|
|
501
|
+
self.clib.alloc_notomoMap4_disc_gggg.argtypes = [
|
|
502
|
+
p_f64, p_f64, p_f64, p_f64, p_f64, p_f64, ct.c_int32,
|
|
503
|
+
ct.c_int32, ct.c_double, ct.c_double, ct.c_int32, ct.c_int32, p_f64, p_f64, ct.c_int32,
|
|
504
|
+
p_i32, p_i32, p_i32, ct.c_int32,
|
|
505
|
+
ct.c_double, ct.c_double, ct.c_int32, ct.c_double, ct.c_double, ct.c_int32,
|
|
506
|
+
p_i32, p_i32, p_i32, ct.c_int32,
|
|
507
|
+
ct.c_int32, ct.c_int32, ct.c_int32, p_f64, ct.c_int32, np.ctypeslib.ndpointer(dtype=np.complex128),
|
|
508
|
+
ct.c_int32, ct.c_int32, np.ctypeslib.ndpointer(dtype=np.float64),
|
|
509
|
+
np.ctypeslib.ndpointer(dtype=np.complex128),np.ctypeslib.ndpointer(dtype=np.complex128),
|
|
510
|
+
np.ctypeslib.ndpointer(dtype=np.complex128),np.ctypeslib.ndpointer(dtype=np.complex128)]
|
|
511
|
+
|
|
512
|
+
# Tree-based estimator of non-tomographic Map^4 statistics (low-mem)
|
|
513
|
+
self.clib.alloc_notomoMap4_tree_gggg.restype = ct.c_void_p
|
|
514
|
+
self.clib.alloc_notomoMap4_tree_gggg.argtypes = [
|
|
515
|
+
p_f64, p_f64, p_f64, p_f64, p_f64, p_f64, ct.c_int32,
|
|
516
|
+
ct.c_int32, ct.c_double, ct.c_double, ct.c_int32, ct.c_int32,
|
|
517
|
+
p_i32, ct.c_int32, p_f64, p_f64, ct.c_int32,
|
|
518
|
+
ct.c_int32, p_f64, p_i32,
|
|
519
|
+
p_f64, p_f64, p_f64, p_f64, p_f64, p_f64,
|
|
520
|
+
p_i32, p_i32, p_i32, ct.c_int32,
|
|
521
|
+
ct.c_double, ct.c_double, ct.c_int32, ct.c_double, ct.c_double, ct.c_int32,
|
|
522
|
+
p_i32, p_i32, p_i32, ct.c_int32,
|
|
523
|
+
ct.c_int32, ct.c_int32, ct.c_int32, p_f64, ct.c_int32, np.ctypeslib.ndpointer(dtype=np.complex128),
|
|
524
|
+
ct.c_int32, ct.c_int32, np.ctypeslib.ndpointer(dtype=np.float64),
|
|
525
|
+
np.ctypeslib.ndpointer(dtype=np.complex128),np.ctypeslib.ndpointer(dtype=np.complex128),
|
|
526
|
+
np.ctypeslib.ndpointer(dtype=np.complex128),np.ctypeslib.ndpointer(dtype=np.complex128)]
|
|
527
|
+
|
|
528
|
+
self.clib.multipoles2npcf_gggg.restype = ct.c_void_p
|
|
529
|
+
self.clib.multipoles2npcf_gggg.argtypes = [
|
|
530
|
+
p_c128, p_c128, p_f64, ct.c_int32,
|
|
531
|
+
ct.c_int32, ct.c_int32, ct.c_int32, p_f64, ct.c_int32, p_f64, ct.c_int32,
|
|
532
|
+
ct.c_int32, p_c128, p_c128]
|
|
533
|
+
|
|
534
|
+
# Transformation between 4PCF from multipole-basis tp real-space basis for a fixed
|
|
535
|
+
# combination of radial bins
|
|
536
|
+
self.clib.multipoles2npcf_gggg_singletheta.restype = ct.c_void_p
|
|
537
|
+
self.clib.multipoles2npcf_gggg_singletheta.argtypes = [
|
|
538
|
+
p_c128, p_c128, ct.c_int32, ct.c_int32,
|
|
539
|
+
ct.c_double, ct.c_double, ct.c_double,
|
|
540
|
+
p_f64, p_f64, ct.c_int32, ct.c_int32,
|
|
541
|
+
ct.c_int32,
|
|
542
|
+
np.ctypeslib.ndpointer(dtype=np.complex128),np.ctypeslib.ndpointer(dtype=np.complex128)]
|
|
543
|
+
|
|
544
|
+
# Transformation between 4PCF from multipole-basis tp real-space basis for a fixed
|
|
545
|
+
# combination of radial bins. Explicitly checks convergence for orders of multipoles included
|
|
546
|
+
self.clib.multipoles2npcf_gggg_singletheta_nconvergence.restype = ct.c_void_p
|
|
547
|
+
self.clib.multipoles2npcf_gggg_singletheta_nconvergence.argtypes = [
|
|
548
|
+
p_c128, p_c128, ct.c_int32, ct.c_int32,
|
|
549
|
+
ct.c_double, ct.c_double, ct.c_double,
|
|
550
|
+
p_f64, p_f64, ct.c_int32, ct.c_int32,
|
|
551
|
+
ct.c_int32,
|
|
552
|
+
np.ctypeslib.ndpointer(dtype=np.complex128),np.ctypeslib.ndpointer(dtype=np.complex128)]
|
|
553
|
+
|
|
554
|
+
# Reconstruction of all 4pcf multipoles from symmetry properties given a set of
|
|
555
|
+
# multipoles with theta1<=theta2<=theta3
|
|
556
|
+
self.clib.getMultipolesFromSymm.restype = ct.c_void_p
|
|
557
|
+
self.clib.getMultipolesFromSymm.argtypes = [
|
|
558
|
+
p_c128, p_c128,
|
|
559
|
+
ct.c_int32, ct.c_int32, p_i32, ct.c_int32,
|
|
560
|
+
np.ctypeslib.ndpointer(dtype=np.complex128),np.ctypeslib.ndpointer(dtype=np.complex128)]
|
|
561
|
+
|
|
562
|
+
# Transformaton between 4pcf multipoles and M4 correlators of Map4 statistics
|
|
563
|
+
self.clib.fourpcfmultipoles2M4correlators.restype = ct.c_void_p
|
|
564
|
+
self.clib.fourpcfmultipoles2M4correlators.argtypes = [
|
|
565
|
+
ct.c_int32, ct.c_int32,
|
|
566
|
+
p_f64, p_f64, ct.c_int32,
|
|
567
|
+
p_f64, ct.c_int32,
|
|
568
|
+
p_f64, p_f64, p_f64, p_f64, ct.c_int32, ct.c_int32,
|
|
569
|
+
ct.c_int32, ct.c_int32,
|
|
570
|
+
p_c128, p_c128, np.ctypeslib.ndpointer(dtype=np.complex128)]
|
|
571
|
+
|
|
572
|
+
# [DEBUG]: Shear 4pt function in terms of xip/xim
|
|
573
|
+
self.clib.gauss4pcf_analytic.restype = ct.c_void_p
|
|
574
|
+
self.clib.gauss4pcf_analytic.argtypes = [
|
|
575
|
+
ct.c_double, ct.c_double, ct.c_double, p_f64, ct.c_int32,
|
|
576
|
+
p_f64, p_f64, ct.c_double, ct.c_double, ct.c_double,
|
|
577
|
+
np.ctypeslib.ndpointer(dtype=np.complex128)]
|
|
578
|
+
|
|
579
|
+
# [DEBUG]: Shear 4pt function in terms of xip/xim, subsampled within the 4pcf bins
|
|
580
|
+
self.clib.gauss4pcf_analytic_integrated.restype = ct.c_void_p
|
|
581
|
+
self.clib.gauss4pcf_analytic_integrated.argtypes = [
|
|
582
|
+
ct.c_int32, ct.c_int32, ct.c_int32, ct.c_int32,
|
|
583
|
+
p_f64, ct.c_int32, p_f64, ct.c_int32,
|
|
584
|
+
p_f64, p_f64, ct.c_double, ct.c_double, ct.c_double,
|
|
585
|
+
np.ctypeslib.ndpointer(dtype=np.complex128)]
|
|
586
|
+
|
|
587
|
+
# [DEBUG]: Map4 via analytic gaussian 4pcf
|
|
588
|
+
self.clib.alloc_notomoMap4_analytic.restype = ct.c_void_p
|
|
589
|
+
self.clib.alloc_notomoMap4_analytic.argtypes = [
|
|
590
|
+
ct.c_double, ct.c_double, ct.c_int32, p_f64, p_f64, ct.c_int32, ct.c_int32,
|
|
591
|
+
p_i32, p_i32, p_i32, ct.c_int32,
|
|
592
|
+
ct.c_int32, p_f64, ct.c_int32,
|
|
593
|
+
p_f64, p_f64, ct.c_double, ct.c_double, ct.c_int32, ct.c_int32,
|
|
594
|
+
np.ctypeslib.ndpointer(dtype=np.complex128)]
|
|
595
|
+
|
|
596
|
+
# [DEBUG]: Map4 filter function for single combination
|
|
597
|
+
self.clib.filter_Map4.restype = ct.c_void_p
|
|
598
|
+
self.clib.filter_Map4.argtypes = [
|
|
599
|
+
ct.c_double, ct.c_double, ct.c_double, ct.c_double, ct.c_double,
|
|
600
|
+
np.ctypeslib.ndpointer(dtype=np.complex128)]
|
|
601
|
+
|
|
602
|
+
# [DEBUG]: Conversion between 4pcf and Map4 for (theta1,theta2,theta3) subset
|
|
603
|
+
self.clib.fourpcf2M4correlators_parallel.restype = ct.c_void_p
|
|
604
|
+
self.clib.fourpcf2M4correlators_parallel.argtypes = [
|
|
605
|
+
ct.c_int32,
|
|
606
|
+
ct.c_double, ct.c_double, ct.c_double, ct.c_double, ct.c_double, ct.c_double,
|
|
607
|
+
p_f64, p_f64, p_f64, p_f64, ct.c_int32, ct.c_int32,
|
|
608
|
+
ct.c_int32,
|
|
609
|
+
np.ctypeslib.ndpointer(dtype=np.complex128), np.ctypeslib.ndpointer(dtype=np.complex128)]
|
|
610
|
+
|
|
611
|
+
############################################################
|
|
612
|
+
## Functions that deal with different projections of NPCF ##
|
|
613
|
+
############################################################
|
|
614
|
+
def _initprojections(self, child):
|
|
615
|
+
assert(child.projection in child.projections_avail)
|
|
616
|
+
child.project = {}
|
|
617
|
+
for proj in child.projections_avail:
|
|
618
|
+
child.project[proj] = {}
|
|
619
|
+
for proj2 in child.projections_avail:
|
|
620
|
+
if proj==proj2:
|
|
621
|
+
child.project[proj][proj2] = lambda: child.npcf
|
|
622
|
+
else:
|
|
623
|
+
child.project[proj][proj2] = None
|
|
624
|
+
|
|
625
|
+
def _projectnpcf(self, child, projection):
|
|
626
|
+
"""
|
|
627
|
+
Projects npcf to a new basis.
|
|
628
|
+
"""
|
|
629
|
+
assert(child.npcf is not None)
|
|
630
|
+
if projection not in child.projections_avail:
|
|
631
|
+
print(f"Projection {projection} is not yet supported.")
|
|
632
|
+
self._print_npcfprojections_avail()
|
|
633
|
+
return
|
|
634
|
+
|
|
635
|
+
projection_func = child.project[child.projection].get(projection)
|
|
636
|
+
if projection_func is not None:
|
|
637
|
+
child.npcf = projection_func()
|
|
638
|
+
child.projection = projection
|
|
639
|
+
else:
|
|
640
|
+
print(f"Projection from {child.projection} to {projection} is not yet implemented.")
|
|
641
|
+
self._print_npcfprojections_avail(child)
|
|
642
|
+
|
|
643
|
+
def _print_npcfprojections_avail(self, child):
|
|
644
|
+
print(f"The following projections are available in the class {child.__class__.__name__}:")
|
|
645
|
+
for proj in child.projections_avail:
|
|
646
|
+
for proj2 in child.projections_avail:
|
|
647
|
+
if child.project[proj].get(proj2) is not None:
|
|
648
|
+
print(f" {proj} --> {proj2}")
|
|
649
|
+
|
|
650
|
+
####################
|
|
651
|
+
## MISC FUNCTIONS ##
|
|
652
|
+
####################
|
|
653
|
+
def _checkcats(self, cats, spins):
|
|
654
|
+
if isinstance(cats, list):
|
|
655
|
+
assert(len(cats)==self.order)
|
|
656
|
+
for els, s in enumerate(self.spins):
|
|
657
|
+
if not isinstance(cats, list):
|
|
658
|
+
thiscat = cats
|
|
659
|
+
else:
|
|
660
|
+
thiscat = cats[els]
|
|
661
|
+
assert(thiscat.spin == s)
|
|
662
|
+
|
|
663
|
+
def _updatetree(self, new_resos):
|
|
664
|
+
|
|
665
|
+
new_resos = np.asarray(new_resos, dtype=np.float64)
|
|
666
|
+
new_nresos = int(len(new_resos))
|
|
667
|
+
|
|
668
|
+
new_redges = np.zeros(len(new_resos)+1)
|
|
669
|
+
new_redges[0] = self.min_sep
|
|
670
|
+
new_redges[-1] = self.max_sep
|
|
671
|
+
for elreso, reso in enumerate(new_resos[1:]):
|
|
672
|
+
new_redges[elreso+1] = self.rmin_pixsize*reso
|
|
673
|
+
_tmpreso = 0
|
|
674
|
+
new_resosatr = np.zeros(self.nbinsr, dtype=np.int32)
|
|
675
|
+
for elbin, rbin in enumerate(self.bin_edges[:-1]):
|
|
676
|
+
if rbin > new_redges[_tmpreso+1]:
|
|
677
|
+
_tmpreso += 1
|
|
678
|
+
new_resosatr[elbin] = _tmpreso
|
|
679
|
+
|
|
680
|
+
self.tree_resos = new_resos
|
|
681
|
+
self.tree_nresos = new_nresos
|
|
682
|
+
self.tree_redges = new_redges
|
|
683
|
+
self.tree_resosatr = new_resosatr
|
|
684
|
+
|
|
685
|
+
|
|
686
|
+
|
|
687
|
+
###############################
|
|
688
|
+
## SECOND - ORDER STATISTICS ##
|
|
689
|
+
###############################
|
|
690
|
+
class GGCorrelation(BinnedNPCF):
|
|
691
|
+
""" Compute second-order correlation functions of spin-2 fields.
|
|
692
|
+
|
|
693
|
+
Parameters
|
|
694
|
+
----------
|
|
695
|
+
min_sep: float
|
|
696
|
+
The smallest distance of each vertex for which the NPCF is computed.
|
|
697
|
+
max_sep: float
|
|
698
|
+
The largest distance of each vertex for which the NPCF is computed.
|
|
699
|
+
|
|
700
|
+
Attributes
|
|
701
|
+
----------
|
|
702
|
+
xip: numpy.ndarray
|
|
703
|
+
The ξ₊ correlation function.
|
|
704
|
+
xim: numpy.ndarray
|
|
705
|
+
The ξ₋ correlation function.
|
|
706
|
+
norm: numpy.ndarray
|
|
707
|
+
The number of weighted pairs.
|
|
708
|
+
npair: numpy.ndarray
|
|
709
|
+
The number of unweighted pairs.
|
|
710
|
+
|
|
711
|
+
Notes
|
|
712
|
+
-----
|
|
713
|
+
Inherits all other parameters and attributes from :class:`BinnedNPCF`.
|
|
714
|
+
Additional child-specific parameters can be passed via ``kwargs``.
|
|
715
|
+
Either ``nbinsr`` or ``binsize`` has to be provided to fix the binning scheme .
|
|
716
|
+
"""
|
|
717
|
+
|
|
718
|
+
def __init__(self, min_sep, max_sep, **kwargs):
|
|
719
|
+
super().__init__(order=2, spins=np.array([2,2], dtype=np.int32), n_cfs=2, min_sep=min_sep, max_sep=max_sep, **kwargs)
|
|
720
|
+
self.projection = None
|
|
721
|
+
self.projections_avail = [None]
|
|
722
|
+
self.nbinsz = None
|
|
723
|
+
self.nzcombis = None
|
|
724
|
+
self.counts = None
|
|
725
|
+
self.xip = None
|
|
726
|
+
self.xim = None
|
|
727
|
+
self.norm = None
|
|
728
|
+
self.npair = None
|
|
729
|
+
|
|
730
|
+
# (Add here any newly implemented projections)
|
|
731
|
+
self._initprojections(self)
|
|
732
|
+
|
|
733
|
+
def saveinst(self, path_save, fname):
|
|
734
|
+
|
|
735
|
+
if not Path(path_save).is_dir():
|
|
736
|
+
raise ValueError('Path to directory does not exist.')
|
|
737
|
+
|
|
738
|
+
np.savez(path_save+fname,
|
|
739
|
+
nbinsz=self.nbinsz,
|
|
740
|
+
min_sep=self.min_sep,
|
|
741
|
+
max_sep=self.max_sep,
|
|
742
|
+
binsr=self.nbinsr,
|
|
743
|
+
method=self.method,
|
|
744
|
+
shuffle_pix=self.shuffle_pix,
|
|
745
|
+
tree_resos=self.tree_resos,
|
|
746
|
+
rmin_pixsize=self.rmin_pixsize,
|
|
747
|
+
resoshift_leafs=self.resoshift_leafs,
|
|
748
|
+
minresoind_leaf=self.minresoind_leaf,
|
|
749
|
+
maxresoind_leaf=self.maxresoind_leaf,
|
|
750
|
+
nthreads=self.nthreads,
|
|
751
|
+
bin_centers=self.bin_centers,
|
|
752
|
+
xip=self.xip,
|
|
753
|
+
xim=self.xim,
|
|
754
|
+
npair=self.npair,
|
|
755
|
+
norm=self.norm)
|
|
756
|
+
|
|
757
|
+
def __process_patches(self, cat, dotomo=True, do_dc=False, rotsignflip=False, apply_edge_correction=False, adjust_tree=False,
|
|
758
|
+
save_patchres=False, save_filebase="", keep_patchres=False):
|
|
759
|
+
|
|
760
|
+
if save_patchres:
|
|
761
|
+
if not Path(save_patchres).is_dir():
|
|
762
|
+
raise ValueError('Path to directory does not exist.')
|
|
763
|
+
|
|
764
|
+
for elp in range(cat.npatches):
|
|
765
|
+
if self._verbose_python:
|
|
766
|
+
print('Doing patch %i/%i'%(elp+1,cat.npatches))
|
|
767
|
+
|
|
768
|
+
# Compute statistics on patch
|
|
769
|
+
pcat = cat.frompatchind(elp,rotsignflip=rotsignflip)
|
|
770
|
+
pcorr = GGCorrelation(
|
|
771
|
+
min_sep=self.min_sep,
|
|
772
|
+
max_sep=self.max_sep,
|
|
773
|
+
nbinsr=self.nbinsr,
|
|
774
|
+
method=self.method,
|
|
775
|
+
shuffle_pix=self.shuffle_pix,
|
|
776
|
+
tree_resos=self.tree_resos,
|
|
777
|
+
rmin_pixsize=self.rmin_pixsize,
|
|
778
|
+
resoshift_leafs=self.resoshift_leafs,
|
|
779
|
+
minresoind_leaf=self.minresoind_leaf,
|
|
780
|
+
maxresoind_leaf=self.maxresoind_leaf,
|
|
781
|
+
nthreads=self.nthreads,
|
|
782
|
+
verbosity=self.verbosity)
|
|
783
|
+
pcorr.process(pcat, dotomo=dotomo, do_dc=do_dc)
|
|
784
|
+
|
|
785
|
+
# Update the total measurement
|
|
786
|
+
if elp == 0:
|
|
787
|
+
self.nbinsz = pcorr.nbinsz
|
|
788
|
+
self.nzcombis = pcorr.nzcombis
|
|
789
|
+
self.bin_centers = np.zeros_like(pcorr.bin_centers)
|
|
790
|
+
self.xip = np.zeros_like(pcorr.xip)
|
|
791
|
+
self.xim = np.zeros_like(pcorr.xim)
|
|
792
|
+
self.norm = np.zeros_like(pcorr.norm)
|
|
793
|
+
self.npair = np.zeros_like(pcorr.norm)
|
|
794
|
+
if keep_patchres:
|
|
795
|
+
centers_patches = np.zeros((cat.npatches, *pcorr.bin_centers.shape), dtype=pcorr.bin_centers.dtype)
|
|
796
|
+
xip_patches = np.zeros((cat.npatches, *pcorr.xip.shape), dtype=pcorr.xip.dtype)
|
|
797
|
+
xim_patches = np.zeros((cat.npatches, *pcorr.xim.shape), dtype=pcorr.xim.dtype)
|
|
798
|
+
norm_patches = np.zeros((cat.npatches, *pcorr.norm.shape), dtype=pcorr.norm.dtype)
|
|
799
|
+
npair_patches = np.zeros((cat.npatches, *pcorr.npair.shape), dtype=pcorr.npair.dtype)
|
|
800
|
+
self.bin_centers += pcorr.norm*pcorr.bin_centers
|
|
801
|
+
self.xip += pcorr.norm*pcorr.xip
|
|
802
|
+
self.xim += pcorr.norm*pcorr.xim
|
|
803
|
+
self.norm += pcorr.norm
|
|
804
|
+
self.npair += pcorr.npair
|
|
805
|
+
if keep_patchres:
|
|
806
|
+
centers_patches[elp] += pcorr.bin_centers
|
|
807
|
+
xip_patches[elp] += pcorr.xip
|
|
808
|
+
xim_patches[elp] += pcorr.xim
|
|
809
|
+
norm_patches[elp] += pcorr.norm
|
|
810
|
+
npair_patches[elp] += pcorr.npair
|
|
811
|
+
if save_patchres:
|
|
812
|
+
pcorr.saveinst(save_patchres, save_filebase+'_patch%i'%elp)
|
|
813
|
+
|
|
814
|
+
# Finalize the measurement on the full footprint
|
|
815
|
+
self.bin_centers /= self.norm
|
|
816
|
+
self.bin_centers_mean = np.mean(self.bin_centers, axis=0)
|
|
817
|
+
self.xip /= self.norm
|
|
818
|
+
self.xim /= self.norm
|
|
819
|
+
self.projection = "xipm"
|
|
820
|
+
|
|
821
|
+
if keep_patchres:
|
|
822
|
+
return centers_patches, xip_patches, xim_patches, norm_patches, npair_patches
|
|
823
|
+
|
|
824
|
+
def process(self, cat, dotomo=True, do_dc=False, rotsignflip=False, adjust_tree=False,
|
|
825
|
+
save_patchres=False, save_filebase="", keep_patchres=False):
|
|
826
|
+
r"""
|
|
827
|
+
Compute a shear 2PCF provided a shape catalog
|
|
828
|
+
|
|
829
|
+
Parameters
|
|
830
|
+
----------
|
|
831
|
+
cat: orpheus.SpinTracerCatalog
|
|
832
|
+
The shape catalog which is processed
|
|
833
|
+
dotomo: bool
|
|
834
|
+
Flag that decides whether the tomographic information in the shape catalog should be used. Defaults to `True`.
|
|
835
|
+
do_dc: bool
|
|
836
|
+
Flag that decides whether to double-count the paircounts. This will have no impact on $\xi_\pm$, but can
|
|
837
|
+
significantly reduce the amplitude of $\xi_x$. Defaults to `False`.
|
|
838
|
+
rotsignflip: bool
|
|
839
|
+
If the shape catalog is has been decomposed in patches, choose whether the rotation angle should be flipped.
|
|
840
|
+
For simulated data this was always ok to set to 'False`. Defaults to `False`.
|
|
841
|
+
adjust_tree: bool
|
|
842
|
+
Overrides the original setup of the tree-approximations in the instance based on the nbar of the shape catalog.
|
|
843
|
+
Not implemented yet, therefore no effect. Has no effect yet. Defaults to `False`
|
|
844
|
+
save_patchres: bool or str
|
|
845
|
+
If the shape catalog is has been decomposed in patches, flag whether to save the GG measurements on the individual patches.
|
|
846
|
+
Note that the path needs to exist, otherwise a `ValueError` is raised. For a flat-sky catalog this parameter
|
|
847
|
+
has no effect. Defaults to `False`
|
|
848
|
+
save_filebase: str
|
|
849
|
+
Base of the filenames in which the patches are saved. The full filename will be `<save_patchres>/<save_filebase>_patchxx.npz`.
|
|
850
|
+
Only has an effect if the shape catalog consists of multiple patches and `save_patchres` is not `False`.
|
|
851
|
+
keep_patchres: bool
|
|
852
|
+
If the catalog consists of multiple patches, returns all measurements on the patches. Defaults to `False`.
|
|
853
|
+
"""
|
|
854
|
+
|
|
855
|
+
# Make sure that in case the catalog is spherical, it has been decomposed into patches
|
|
856
|
+
if cat.geometry == 'spherical' and cat.patchinds is None:
|
|
857
|
+
raise ValueError('Error: Spherical catalog needs to be first decomposed into patches using the Catalog._topatches method.')
|
|
858
|
+
|
|
859
|
+
# Catalog consist of multiple patches
|
|
860
|
+
if cat.patchinds is not None:
|
|
861
|
+
return self.__process_patches(cat, dotomo=dotomo, do_dc=do_dc, rotsignflip=rotsignflip, adjust_tree=adjust_tree,
|
|
862
|
+
save_patchres=save_patchres, save_filebase=save_filebase, keep_patchres=keep_patchres)
|
|
863
|
+
# Catalog does not consist of patches
|
|
864
|
+
else:
|
|
865
|
+
# Prechecks
|
|
866
|
+
self._checkcats(cat, self.spins)
|
|
867
|
+
if not dotomo:
|
|
868
|
+
self.nbinsz = 1
|
|
869
|
+
old_zbins = cat.zbins[:]
|
|
870
|
+
cat.zbins = np.zeros(cat.ngal, dtype=np.int32)
|
|
871
|
+
self.nzcombis = 1
|
|
872
|
+
else:
|
|
873
|
+
self.nbinsz = cat.nbinsz
|
|
874
|
+
zbins = cat.zbins
|
|
875
|
+
self.nzcombis = self.nbinsz*self.nbinsz
|
|
876
|
+
|
|
877
|
+
z2r = self.nbinsz*self.nbinsz*self.nbinsr
|
|
878
|
+
sz2r = (self.nbinsz*self.nbinsz, self.nbinsr)
|
|
879
|
+
bin_centers = np.zeros(z2r).astype(np.float64)
|
|
880
|
+
xip = np.zeros(z2r).astype(np.complex128)
|
|
881
|
+
xim = np.zeros(z2r).astype(np.complex128)
|
|
882
|
+
norm = np.zeros(z2r).astype(np.float64)
|
|
883
|
+
npair = np.zeros(z2r).astype(np.int64)
|
|
884
|
+
|
|
885
|
+
args_basesetup = (np.float64(self.min_sep), np.float64(self.max_sep), np.int32(self.nbinsr), )
|
|
886
|
+
|
|
887
|
+
cutfirst = np.int32(self.tree_resos[0]==0.)
|
|
888
|
+
mhash = cat.multihash(dpixs=self.tree_resos[cutfirst:], dpix_hash=self.tree_resos[-1],
|
|
889
|
+
shuffle=self.shuffle_pix, w2field=True, normed=True)
|
|
890
|
+
ngal_resos, pos1s, pos2s, weights, zbins, isinners, allfields, index_matchers, pixs_galind_bounds, pix_gals, dpixs1_true, dpixs2_true = mhash
|
|
891
|
+
weight_resos = np.concatenate(weights).astype(np.float64)
|
|
892
|
+
pos1_resos = np.concatenate(pos1s).astype(np.float64)
|
|
893
|
+
pos2_resos = np.concatenate(pos2s).astype(np.float64)
|
|
894
|
+
zbin_resos = np.concatenate(zbins).astype(np.int32)
|
|
895
|
+
isinner_resos = np.concatenate(isinners).astype(np.float64)
|
|
896
|
+
e1_resos = np.concatenate([allfields[i][0] for i in range(len(allfields))]).astype(np.float64)
|
|
897
|
+
e2_resos = np.concatenate([allfields[i][1] for i in range(len(allfields))]).astype(np.float64)
|
|
898
|
+
index_matcher = np.concatenate(index_matchers).astype(np.int32)
|
|
899
|
+
pixs_galind_bounds = np.concatenate(pixs_galind_bounds).astype(np.int32)
|
|
900
|
+
pix_gals = np.concatenate(pix_gals).astype(np.int32)
|
|
901
|
+
index_matcher_flat = np.argwhere(cat.index_matcher>-1).flatten()
|
|
902
|
+
nregions = len(index_matcher_flat)
|
|
903
|
+
|
|
904
|
+
args_treeresos = (np.int32(self.tree_nresos), np.int32(self.tree_nresos-cutfirst),
|
|
905
|
+
dpixs1_true.astype(np.float64), dpixs2_true.astype(np.float64), self.tree_redges,
|
|
906
|
+
np.int32(self.resoshift_leafs), np.int32(self.minresoind_leaf),
|
|
907
|
+
np.int32(self.maxresoind_leaf), np.array(ngal_resos, dtype=np.int32), )
|
|
908
|
+
args_resos = (isinner_resos, weight_resos, pos1_resos, pos2_resos, e1_resos, e2_resos, zbin_resos,
|
|
909
|
+
index_matcher, pixs_galind_bounds, pix_gals, )
|
|
910
|
+
args_hash = (np.float64(cat.pix1_start), np.float64(cat.pix1_d), np.int32(cat.pix1_n),
|
|
911
|
+
np.float64(cat.pix2_start), np.float64(cat.pix2_d), np.int32(cat.pix2_n),
|
|
912
|
+
np.int32(nregions), index_matcher_flat.astype(np.int32),)
|
|
913
|
+
args_binning = (np.float64(self.min_sep), np.float64(self.max_sep), np.int32(self.nbinsr), np.int32(do_dc))
|
|
914
|
+
args_output = (bin_centers, xip, xim, norm, npair, )
|
|
915
|
+
func = self.clib.alloc_xipm_doubletree
|
|
916
|
+
args = (*args_treeresos,
|
|
917
|
+
np.int32(self.nbinsz),
|
|
918
|
+
*args_resos,
|
|
919
|
+
*args_hash,
|
|
920
|
+
*args_binning,
|
|
921
|
+
np.int32(self.nthreads),
|
|
922
|
+
np.int32(self._verbose_c)+np.int32(self._verbose_debug),
|
|
923
|
+
*args_output)
|
|
924
|
+
|
|
925
|
+
func(*args)
|
|
926
|
+
|
|
927
|
+
self.bin_centers = bin_centers.reshape(sz2r)
|
|
928
|
+
self.bin_centers_mean = np.mean(self.bin_centers, axis=0)
|
|
929
|
+
self.npair = npair.reshape(sz2r)
|
|
930
|
+
self.norm = norm.reshape(sz2r)
|
|
931
|
+
self.xip = xip.reshape(sz2r)
|
|
932
|
+
self.xim = xim.reshape(sz2r)
|
|
933
|
+
self.projection = "xipm"
|
|
934
|
+
|
|
935
|
+
if not dotomo:
|
|
936
|
+
cat.zbins = old_zbins
|
|
937
|
+
|
|
938
|
+
|
|
939
|
+
def computeMap2(self, radii, tofile=False):
|
|
940
|
+
""" Computes second-order aperture mass statistics given the shear correlation functions.
|
|
941
|
+
Uses the Crittenden 2002 filter.
|
|
942
|
+
"""
|
|
943
|
+
|
|
944
|
+
Tp = lambda x: 1./128. * (x**4-16*x**2+32) * np.exp(-x**2/4.)
|
|
945
|
+
Tm = lambda x: 1./128. * (x**4) * np.exp(-x**2/4.)
|
|
946
|
+
result = np.zeros((4, self.nzcombis, len(radii)), dtype=float)
|
|
947
|
+
for elr, R in enumerate(radii):
|
|
948
|
+
thetared = self.bin_centers/R
|
|
949
|
+
pref = self.binsize*thetared**2/2.
|
|
950
|
+
t1 = np.sum(pref*(Tp(thetared)*self.xip + Tm(thetared)*self.xim), axis=1)
|
|
951
|
+
t2 = np.sum(pref*(Tp(thetared)*self.xip - Tm(thetared)*self.xim), axis=1)
|
|
952
|
+
result[0,:,elr] = t1.real # Map2
|
|
953
|
+
result[1,:,elr] = t1.imag # MapMx
|
|
954
|
+
result[2,:,elr] = t2.real # Mx2
|
|
955
|
+
result[3,:,elr] = t2.imag # MxMap (Difference from MapMx gives ~level of estimator uncertainty)
|
|
956
|
+
|
|
957
|
+
return result
|
|
958
|
+
|
|
959
|
+
|
|
960
|
+
|
|
961
|
+
|
|
962
|
+
##############################
|
|
963
|
+
## THIRD - ORDER STATISTICS ##
|
|
964
|
+
##############################
|
|
965
|
+
class GGGCorrelation(BinnedNPCF):
|
|
966
|
+
r""" Class containing methods to measure and and obtain statistics that are built
|
|
967
|
+
from third-order shear correlation functions.
|
|
968
|
+
|
|
969
|
+
Attributes
|
|
970
|
+
----------
|
|
971
|
+
n_cfs: int
|
|
972
|
+
The number of independent components of the NPCF.
|
|
973
|
+
min_sep: float
|
|
974
|
+
The smallest distance of each vertex for which the NPCF is computed.
|
|
975
|
+
max_sep: float
|
|
976
|
+
The largest distance of each vertex for which the NPCF is computed.
|
|
977
|
+
|
|
978
|
+
Notes
|
|
979
|
+
-----
|
|
980
|
+
Inherits all other parameters and attributes from :class:`BinnedNPCF`.
|
|
981
|
+
Additional child-specific parameters can be passed via ``kwargs``.
|
|
982
|
+
Either ``nbinsr`` or ``binsize`` has to be provided to fix the binning scheme .
|
|
983
|
+
"""
|
|
984
|
+
|
|
985
|
+
def __init__(self, n_cfs, min_sep, max_sep, **kwargs):
|
|
986
|
+
|
|
987
|
+
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)
|
|
988
|
+
self.nmax = self.nmaxs[0]
|
|
989
|
+
self.phi = self.phis[0]
|
|
990
|
+
self.projection = None
|
|
991
|
+
self.projections_avail = [None, "X", "Centroid"]
|
|
992
|
+
self.nbinsz = None
|
|
993
|
+
self.nzcombis = None
|
|
994
|
+
|
|
995
|
+
# (Add here any newly implemented projections)
|
|
996
|
+
self._initprojections(self)
|
|
997
|
+
self.project["X"]["Centroid"] = self._x2centroid
|
|
998
|
+
|
|
999
|
+
def saveinst(self, path_save, fname):
|
|
1000
|
+
|
|
1001
|
+
if not Path(path_save).is_dir():
|
|
1002
|
+
raise ValueError('Path to directory does not exist.')
|
|
1003
|
+
|
|
1004
|
+
np.savez(path_save+fname,
|
|
1005
|
+
nbinsz=self.nbinsz,
|
|
1006
|
+
min_sep=self.min_sep,
|
|
1007
|
+
max_sep=self.max_sep,
|
|
1008
|
+
binsr=self.nbinsr,
|
|
1009
|
+
nbinsphi=self.nbinsphi,
|
|
1010
|
+
nmaxs=self.nmaxs,
|
|
1011
|
+
method=self.method,
|
|
1012
|
+
multicountcorr=self.multicountcorr,
|
|
1013
|
+
shuffle_pix=self.shuffle_pix,
|
|
1014
|
+
tree_resos=self.tree_resos,
|
|
1015
|
+
rmin_pixsize=self.rmin_pixsize,
|
|
1016
|
+
resoshift_leafs=self.resoshift_leafs,
|
|
1017
|
+
minresoind_leaf=self.minresoind_leaf,
|
|
1018
|
+
maxresoind_leaf=self.maxresoind_leaf,
|
|
1019
|
+
nthreads=self.nthreads,
|
|
1020
|
+
bin_centers=self.bin_centers,
|
|
1021
|
+
npcf_multipoles=self.npcf_multipoles,
|
|
1022
|
+
npcf_multipoles_norm=self.npcf_multipoles_norm)
|
|
1023
|
+
|
|
1024
|
+
def __process_patches(self, cat, dotomo=True, rotsignflip=False, apply_edge_correction=False, adjust_tree=False,
|
|
1025
|
+
save_patchres=False, save_filebase="", keep_patchres=False):
|
|
1026
|
+
|
|
1027
|
+
if save_patchres:
|
|
1028
|
+
if not Path(save_patchres).is_dir():
|
|
1029
|
+
raise ValueError('Path to directory does not exist.')
|
|
1030
|
+
|
|
1031
|
+
for elp in range(cat.npatches):
|
|
1032
|
+
if self._verbose_python:
|
|
1033
|
+
print('Doing patch %i/%i'%(elp+1,cat.npatches))
|
|
1034
|
+
|
|
1035
|
+
# Compute statistics on patch
|
|
1036
|
+
pcat = cat.frompatchind(elp,rotsignflip=rotsignflip)
|
|
1037
|
+
pcorr = GGGCorrelation(
|
|
1038
|
+
n_cfs=self.n_cfs,
|
|
1039
|
+
min_sep=self.min_sep,
|
|
1040
|
+
max_sep=self.max_sep,
|
|
1041
|
+
nbinsr=self.nbinsr,
|
|
1042
|
+
nbinsphi=self.nbinsphi,
|
|
1043
|
+
nmaxs=self.nmaxs,
|
|
1044
|
+
method=self.method,
|
|
1045
|
+
multicountcorr=self.multicountcorr,
|
|
1046
|
+
shuffle_pix=self.shuffle_pix,
|
|
1047
|
+
tree_resos=self.tree_resos,
|
|
1048
|
+
rmin_pixsize=self.rmin_pixsize,
|
|
1049
|
+
resoshift_leafs=self.resoshift_leafs,
|
|
1050
|
+
minresoind_leaf=self.minresoind_leaf,
|
|
1051
|
+
maxresoind_leaf=self.maxresoind_leaf,
|
|
1052
|
+
nthreads=self.nthreads,
|
|
1053
|
+
verbosity=self.verbosity)
|
|
1054
|
+
pcorr.process(pcat, dotomo=dotomo)
|
|
1055
|
+
|
|
1056
|
+
# Update the total measurement
|
|
1057
|
+
if elp == 0:
|
|
1058
|
+
self.nbinsz = pcorr.nbinsz
|
|
1059
|
+
self.nzcombis = pcorr.nzcombis
|
|
1060
|
+
self.bin_centers = np.zeros_like(pcorr.bin_centers)
|
|
1061
|
+
self.npcf_multipoles = np.zeros_like(pcorr.npcf_multipoles)
|
|
1062
|
+
self.npcf_multipoles_norm = np.zeros_like(pcorr.npcf_multipoles_norm)
|
|
1063
|
+
_footnorm = np.zeros_like(pcorr.bin_centers)
|
|
1064
|
+
if keep_patchres:
|
|
1065
|
+
centers_patches = np.zeros((cat.npatches, *pcorr.bin_centers.shape), dtype=pcorr.bin_centers.dtype)
|
|
1066
|
+
npcf_multipoles_patches = np.zeros((cat.npatches, *pcorr.npcf_multipoles.shape), dtype=pcorr.npcf_multipoles.dtype)
|
|
1067
|
+
npcf_multipoles_norm_patches = np.zeros((cat.npatches, *pcorr.npcf_multipoles_norm.shape), dtype=pcorr.npcf_multipoles_norm.dtype)
|
|
1068
|
+
_shelltriplets = np.array([[pcorr.npcf_multipoles_norm[0,z*self.nbinsz*self.nbinsz+z*self.nbinsz+z,i,i].real
|
|
1069
|
+
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
|
|
1070
|
+
# Rough estimate of scaling of pair counts based on zeroth multipole of triplets. Note that we might get nans here due to numerical
|
|
1071
|
+
# inaccuracies in the multiple counting corrections for bins with zero triplets, so we force those values to be zero.
|
|
1072
|
+
_patchnorm = np.nan_to_num(np.sqrt(_shelltriplets))
|
|
1073
|
+
self.bin_centers += _patchnorm*pcorr.bin_centers
|
|
1074
|
+
_footnorm += _patchnorm
|
|
1075
|
+
self.npcf_multipoles += pcorr.npcf_multipoles
|
|
1076
|
+
self.npcf_multipoles_norm += pcorr.npcf_multipoles_norm
|
|
1077
|
+
if keep_patchres:
|
|
1078
|
+
centers_patches[elp] += pcorr.bin_centers
|
|
1079
|
+
npcf_multipoles_patches[elp] += pcorr.npcf_multipoles
|
|
1080
|
+
npcf_multipoles_norm_patches[elp] += pcorr.npcf_multipoles_norm
|
|
1081
|
+
if save_patchres:
|
|
1082
|
+
pcorr.saveinst(save_patchres, save_filebase+'_patch%i'%elp)
|
|
1083
|
+
|
|
1084
|
+
# Finalize the measurement on the full footprint
|
|
1085
|
+
self.bin_centers = np.divide(self.bin_centers,_footnorm, out=np.zeros_like(self.bin_centers), where=_footnorm>0)
|
|
1086
|
+
self.bin_centers_mean = np.mean(self.bin_centers,axis=0)
|
|
1087
|
+
self.projection = "X"
|
|
1088
|
+
|
|
1089
|
+
if keep_patchres:
|
|
1090
|
+
return centers_patches, npcf_multipoles_patches, npcf_multipoles_norm_patches
|
|
1091
|
+
|
|
1092
|
+
|
|
1093
|
+
def process(self, cat, dotomo=True, rotsignflip=False, apply_edge_correction=False, adjust_tree=False,
|
|
1094
|
+
save_patchres=False, save_filebase="", keep_patchres=False):
|
|
1095
|
+
r"""
|
|
1096
|
+
Compute a shear 3PCF provided a shape catalog
|
|
1097
|
+
|
|
1098
|
+
Parameters
|
|
1099
|
+
----------
|
|
1100
|
+
cat: orpheus.SpinTracerCatalog
|
|
1101
|
+
The shape catalog which is processed
|
|
1102
|
+
dotomo: bool
|
|
1103
|
+
Flag that decides whether the tomographic information in the shape catalog should be used. Defaults to `True`.
|
|
1104
|
+
rotsignflip: bool
|
|
1105
|
+
If the shape catalog is has been decomposed in patches, choose whether the rotation angle should be flipped.
|
|
1106
|
+
For simulated data this was always ok to set to 'False`. Has no effect yet. Defaults to `False`.
|
|
1107
|
+
apply_edge_correction: bool
|
|
1108
|
+
Flag that decides how the NPCF in the real space basis is computed.
|
|
1109
|
+
* If set to `True` the computation is done via edge-correcting the GGG-multipoles
|
|
1110
|
+
* If set to `False` both GGG and NNN are transformed separately and the ratio is done in the real-space basis
|
|
1111
|
+
Defaults to `False`.
|
|
1112
|
+
adjust_tree: bool
|
|
1113
|
+
Overrides the original setup of the tree-approximations in the instance based on the nbar of the shape catalog.
|
|
1114
|
+
Not implemented yet, therefore no effect. Has no effect yet. Defaults to `False`
|
|
1115
|
+
save_patchres: bool or str
|
|
1116
|
+
If the shape catalog is has been decomposed in patches, flag whether to save the GGG measurements on the individual patches.
|
|
1117
|
+
Note that the path needs to exist, otherwise a `ValueError` is raised. For a flat-sky catalog this parameter
|
|
1118
|
+
has no effect. Defaults to `False`
|
|
1119
|
+
save_filebase: str
|
|
1120
|
+
Base of the filenames in which the patches are saved. The full filename will be `<save_patchres>/<save_filebase>_patchxx.npz`.
|
|
1121
|
+
Only has an effect if the shape catalog consists of multiple patches and `save_patchres` is not `False`.
|
|
1122
|
+
keep_patchres: bool
|
|
1123
|
+
If the catalog consists of multiple patches, returns all measurements on the patches. Defaults to `False`.
|
|
1124
|
+
"""
|
|
1125
|
+
|
|
1126
|
+
# Make sure that in case the catalog is spherical, it has been decomposed into patches
|
|
1127
|
+
if cat.geometry == 'spherical' and cat.patchinds is None:
|
|
1128
|
+
raise ValueError('Error: Spherical catalog needs to be first decomposed into patches using the Catalog._topatches method.')
|
|
1129
|
+
|
|
1130
|
+
# Catalog consist of multiple patches
|
|
1131
|
+
if cat.patchinds is not None:
|
|
1132
|
+
return self.__process_patches(cat, dotomo=dotomo, rotsignflip=rotsignflip,
|
|
1133
|
+
apply_edge_correction=apply_edge_correction, adjust_tree=adjust_tree,
|
|
1134
|
+
save_patchres=save_patchres, save_filebase=save_filebase, keep_patchres=keep_patchres)
|
|
1135
|
+
|
|
1136
|
+
# Catalog does not consist of patches
|
|
1137
|
+
else:
|
|
1138
|
+
self._checkcats(cat, self.spins)
|
|
1139
|
+
if not dotomo:
|
|
1140
|
+
self.nbinsz = 1
|
|
1141
|
+
old_zbins = cat.zbins[:]
|
|
1142
|
+
cat.zbins = np.zeros(cat.ngal, dtype=np.int32)
|
|
1143
|
+
self.nzcombis = 1
|
|
1144
|
+
else:
|
|
1145
|
+
self.nbinsz = cat.nbinsz
|
|
1146
|
+
zbins = cat.zbins
|
|
1147
|
+
self.nzcombis = self.nbinsz*self.nbinsz*self.nbinsz
|
|
1148
|
+
if adjust_tree:
|
|
1149
|
+
nbar = cat.ngal/(cat.len1*cat.len2)
|
|
1150
|
+
|
|
1151
|
+
sc = (4,self.nmax+1,self.nzcombis,self.nbinsr,self.nbinsr)
|
|
1152
|
+
sn = (self.nmax+1,self.nzcombis,self.nbinsr,self.nbinsr)
|
|
1153
|
+
szr = (self.nbinsz, self.nbinsr)
|
|
1154
|
+
bin_centers = np.zeros(self.nbinsz*self.nbinsr).astype(np.float64)
|
|
1155
|
+
threepcfs_n = np.zeros(4*(self.nmax+1)*self.nzcombis*self.nbinsr*self.nbinsr).astype(np.complex128)
|
|
1156
|
+
threepcfsnorm_n = np.zeros((self.nmax+1)*self.nzcombis*self.nbinsr*self.nbinsr).astype(np.complex128)
|
|
1157
|
+
args_basecat = (cat.isinner.astype(np.float64), cat.weight, cat.pos1, cat.pos2, cat.tracer_1, cat.tracer_2,
|
|
1158
|
+
cat.zbins.astype(np.int32), np.int32(self.nbinsz), np.int32(cat.ngal), )
|
|
1159
|
+
args_basesetup = (np.int32(0), np.int32(self.nmax), np.float64(self.min_sep),
|
|
1160
|
+
np.float64(self.max_sep), np.array([-1.]).astype(np.float64),
|
|
1161
|
+
np.int32(self.nbinsr), np.int32(self.multicountcorr), )
|
|
1162
|
+
if self.method=="Discrete":
|
|
1163
|
+
if not cat.hasspatialhash:
|
|
1164
|
+
cat.build_spatialhash(dpix=max(1.,self.max_sep//10.))
|
|
1165
|
+
args_pixgrid = (np.float64(cat.pix1_start), np.float64(cat.pix1_d), np.int32(cat.pix1_n),
|
|
1166
|
+
np.float64(cat.pix2_start), np.float64(cat.pix2_d), np.int32(cat.pix2_n), )
|
|
1167
|
+
args = (*args_basecat,
|
|
1168
|
+
*args_basesetup,
|
|
1169
|
+
cat.index_matcher,
|
|
1170
|
+
cat.pixs_galind_bounds,
|
|
1171
|
+
cat.pix_gals,
|
|
1172
|
+
*args_pixgrid,
|
|
1173
|
+
np.int32(self.nthreads),
|
|
1174
|
+
np.int32(self._verbose_c),
|
|
1175
|
+
bin_centers,
|
|
1176
|
+
threepcfs_n,
|
|
1177
|
+
threepcfsnorm_n)
|
|
1178
|
+
func = self.clib.alloc_Gammans_discrete_ggg
|
|
1179
|
+
elif self.method in ["Tree", "BaseTree", "DoubleTree"]:
|
|
1180
|
+
if self._verbose_debug:
|
|
1181
|
+
print("Doing multihash")
|
|
1182
|
+
cutfirst = np.int32(self.tree_resos[0]==0.)
|
|
1183
|
+
mhash = cat.multihash(dpixs=self.tree_resos[cutfirst:], dpix_hash=self.tree_resos[-1],
|
|
1184
|
+
shuffle=self.shuffle_pix, w2field=True, normed=True)
|
|
1185
|
+
ngal_resos, pos1s, pos2s, weights, zbins, isinners, allfields, index_matchers, pixs_galind_bounds, pix_gals, dpixs1_true, dpixs2_true = mhash
|
|
1186
|
+
weight_resos = np.concatenate(weights).astype(np.float64)
|
|
1187
|
+
pos1_resos = np.concatenate(pos1s).astype(np.float64)
|
|
1188
|
+
pos2_resos = np.concatenate(pos2s).astype(np.float64)
|
|
1189
|
+
zbin_resos = np.concatenate(zbins).astype(np.int32)
|
|
1190
|
+
isinner_resos = np.concatenate(isinners).astype(np.float64)
|
|
1191
|
+
e1_resos = np.concatenate([allfields[i][0] for i in range(len(allfields))]).astype(np.float64)
|
|
1192
|
+
e2_resos = np.concatenate([allfields[i][1] for i in range(len(allfields))]).astype(np.float64)
|
|
1193
|
+
_weightsq_resos = np.concatenate([allfields[i][2] for i in range(len(allfields))]).astype(np.float64)
|
|
1194
|
+
weightsq_resos = _weightsq_resos*weight_resos # As in reduce we renorm all the fields --> need to `unrenorm'
|
|
1195
|
+
index_matcher = np.concatenate(index_matchers).astype(np.int32)
|
|
1196
|
+
pixs_galind_bounds = np.concatenate(pixs_galind_bounds).astype(np.int32)
|
|
1197
|
+
pix_gals = np.concatenate(pix_gals).astype(np.int32)
|
|
1198
|
+
args_pixgrid = (np.float64(cat.pix1_start), np.float64(cat.pix1_d), np.int32(cat.pix1_n),
|
|
1199
|
+
np.float64(cat.pix2_start), np.float64(cat.pix2_d), np.int32(cat.pix2_n), )
|
|
1200
|
+
args_resos = (weight_resos, pos1_resos, pos2_resos, e1_resos, e2_resos, zbin_resos, weightsq_resos,
|
|
1201
|
+
index_matcher, pixs_galind_bounds, pix_gals, )
|
|
1202
|
+
args_output = (bin_centers, threepcfs_n, threepcfsnorm_n, )
|
|
1203
|
+
if self._verbose_debug:
|
|
1204
|
+
print("Doing %s"%self.method)
|
|
1205
|
+
if self.method=="Tree":
|
|
1206
|
+
args = (*args_basecat,
|
|
1207
|
+
np.int32(self.tree_nresos),
|
|
1208
|
+
self.tree_redges,
|
|
1209
|
+
np.array(ngal_resos, dtype=np.int32),
|
|
1210
|
+
*args_resos,
|
|
1211
|
+
*args_pixgrid,
|
|
1212
|
+
*args_basesetup,
|
|
1213
|
+
np.int32(self.nthreads),
|
|
1214
|
+
np.int32(self._verbose_c),
|
|
1215
|
+
*args_output)
|
|
1216
|
+
func = self.clib.alloc_Gammans_tree_ggg
|
|
1217
|
+
if self.method in ["BaseTree", "DoubleTree"]:
|
|
1218
|
+
args_resos = (isinner_resos, ) + args_resos
|
|
1219
|
+
index_matcher_flat = np.argwhere(cat.index_matcher>-1).flatten()
|
|
1220
|
+
nregions = len(index_matcher_flat)
|
|
1221
|
+
# Select regions with at least one inner galaxy (TODO: Optimize)
|
|
1222
|
+
filledregions = []
|
|
1223
|
+
for elregion in range(nregions):
|
|
1224
|
+
_ = cat.pix_gals[cat.pixs_galind_bounds[elregion]:cat.pixs_galind_bounds[elregion+1]]
|
|
1225
|
+
if np.sum(cat.isinner[_])>0:filledregions.append(elregion)
|
|
1226
|
+
filledregions = np.asarray(filledregions, dtype=np.int32)
|
|
1227
|
+
nfilledregions = np.int32(len(filledregions))
|
|
1228
|
+
args_regions = (index_matcher_flat.astype(np.int32), np.int32(nregions), filledregions, nfilledregions, )
|
|
1229
|
+
args_basesetup_dtree = (np.int32(self.nmax), np.float64(self.min_sep), np.float64(self.max_sep),
|
|
1230
|
+
np.int32(self.nbinsr), np.int32(self.multicountcorr), )
|
|
1231
|
+
args_treeresos = (np.int32(self.tree_nresos), np.int32(self.tree_nresos-cutfirst),
|
|
1232
|
+
dpixs1_true.astype(np.float64), dpixs2_true.astype(np.float64), self.tree_redges, )
|
|
1233
|
+
if self.method=="BaseTree":
|
|
1234
|
+
func = self.clib.alloc_Gammans_basetree_ggg
|
|
1235
|
+
if self.method=="DoubleTree":
|
|
1236
|
+
args_leafs = (np.int32(self.resoshift_leafs), np.int32(self.minresoind_leaf),
|
|
1237
|
+
np.int32(self.maxresoind_leaf), )
|
|
1238
|
+
args_treeresos = args_treeresos + args_leafs
|
|
1239
|
+
func = self.clib.alloc_Gammans_doubletree_ggg
|
|
1240
|
+
args = (*args_treeresos,
|
|
1241
|
+
np.array(ngal_resos, dtype=np.int32),
|
|
1242
|
+
np.int32(self.nbinsz),
|
|
1243
|
+
*args_resos,
|
|
1244
|
+
*args_pixgrid,
|
|
1245
|
+
*args_regions,
|
|
1246
|
+
*args_basesetup_dtree,
|
|
1247
|
+
np.int32(self.nthreads),
|
|
1248
|
+
np.int32(self._verbose_c),
|
|
1249
|
+
*args_output)
|
|
1250
|
+
func(*args)
|
|
1251
|
+
|
|
1252
|
+
self.bin_centers = bin_centers.reshape(szr)
|
|
1253
|
+
self.bin_centers_mean = np.mean(self.bin_centers, axis=0)
|
|
1254
|
+
self.npcf_multipoles = threepcfs_n.reshape(sc)
|
|
1255
|
+
self.npcf_multipoles_norm = threepcfsnorm_n.reshape(sn)
|
|
1256
|
+
self.projection = "X"
|
|
1257
|
+
|
|
1258
|
+
if apply_edge_correction:
|
|
1259
|
+
self.edge_correction()
|
|
1260
|
+
|
|
1261
|
+
if not dotomo:
|
|
1262
|
+
cat.zbins = old_zbins
|
|
1263
|
+
|
|
1264
|
+
|
|
1265
|
+
def edge_correction(self, ret_matrices=False):
|
|
1266
|
+
|
|
1267
|
+
def gen_M_matrix(thet1,thet2,threepcf_n_norm):
|
|
1268
|
+
nvals, ntheta, _ = threepcf_n_norm.shape
|
|
1269
|
+
nmax = (nvals-1)//2
|
|
1270
|
+
narr = np.arange(-nmax,nmax+1, dtype=np.int)
|
|
1271
|
+
nextM = np.zeros((nvals,nvals))
|
|
1272
|
+
for ind, ell in enumerate(narr):
|
|
1273
|
+
lminusn = ell-narr
|
|
1274
|
+
sel = np.logical_and(lminusn+nmax>=0, lminusn+nmax<nvals)
|
|
1275
|
+
nextM[ind,sel] = threepcf_n_norm[(lminusn+nmax)[sel],thet1,thet2].real / threepcf_n_norm[nmax,thet1,thet2].real
|
|
1276
|
+
return nextM
|
|
1277
|
+
|
|
1278
|
+
nvals, nzcombis, ntheta, _ = self.npcf_multipoles_norm.shape
|
|
1279
|
+
nmax = nvals-1
|
|
1280
|
+
threepcf_n_full = np.zeros((4,2*nmax+1, nzcombis, ntheta, ntheta), dtype=complex)
|
|
1281
|
+
threepcf_n_norm_full = np.zeros((2*nmax+1, nzcombis, ntheta, ntheta), dtype=complex)
|
|
1282
|
+
threepcf_n_corr = np.zeros(threepcf_n_full.shape, dtype=np.complex)
|
|
1283
|
+
threepcf_n_full[:,nmax:] = self.npcf_multipoles
|
|
1284
|
+
threepcf_n_norm_full[nmax:] = self.npcf_multipoles_norm
|
|
1285
|
+
for nextn in range(1,nvals):
|
|
1286
|
+
threepcf_n_full[0,nmax-nextn] = self.npcf_multipoles[0,nextn].transpose(0,2,1)
|
|
1287
|
+
threepcf_n_full[1,nmax-nextn] = self.npcf_multipoles[1,nextn].transpose(0,2,1)
|
|
1288
|
+
threepcf_n_full[2,nmax-nextn] = self.npcf_multipoles[3,nextn].transpose(0,2,1)
|
|
1289
|
+
threepcf_n_full[3,nmax-nextn] = self.npcf_multipoles[2,nextn].transpose(0,2,1)
|
|
1290
|
+
threepcf_n_norm_full[nmax-nextn] = self.npcf_multipoles_norm[nextn].transpose(0,2,1)
|
|
1291
|
+
|
|
1292
|
+
if ret_matrices:
|
|
1293
|
+
mats = np.zeros((nzcombis,ntheta,ntheta,nvals,nvals))
|
|
1294
|
+
for indz in range(nzcombis):
|
|
1295
|
+
#sys.stdout.write("%i"%indz)
|
|
1296
|
+
for thet1 in range(ntheta):
|
|
1297
|
+
for thet2 in range(ntheta):
|
|
1298
|
+
nextM = gen_M_matrix(thet1,thet2,threepcf_n_norm_full[:,indz])
|
|
1299
|
+
nextM_inv = np.linalg.inv(nextM)
|
|
1300
|
+
if ret_matrices:
|
|
1301
|
+
mats[indz,thet1,thet2] = nextM
|
|
1302
|
+
for i in range(4):
|
|
1303
|
+
threepcf_n_corr[i,:,indz,thet1,thet2] = np.matmul(nextM_inv,threepcf_n_full[i,:,indz,thet1,thet2])
|
|
1304
|
+
|
|
1305
|
+
self.npcf_multipoles = threepcf_n_corr[:,nmax:]
|
|
1306
|
+
self.is_edge_corrected = True
|
|
1307
|
+
|
|
1308
|
+
if ret_matrices:
|
|
1309
|
+
return threepcf_n_corr[:,nmax:], mats
|
|
1310
|
+
|
|
1311
|
+
# Legacy transform in pure python -- now upgraded to .c
|
|
1312
|
+
def _multipoles2npcf_py(self):
|
|
1313
|
+
|
|
1314
|
+
_, nzcombis, rbins, rbins = np.shape(self.npcf_multipoles[0])
|
|
1315
|
+
self.npcf = np.zeros((4, nzcombis, rbins, rbins, len(self.phi)), dtype=complex)
|
|
1316
|
+
self.npcf_norm = np.zeros((nzcombis, rbins, rbins, len(self.phi)), dtype=complex)
|
|
1317
|
+
ztiler = np.arange(self.nbinsz*self.nbinsz*self.nbinsz).reshape(
|
|
1318
|
+
(self.nbinsz,self.nbinsz,self.nbinsz)).transpose(0,2,1).flatten().astype(np.int32)
|
|
1319
|
+
|
|
1320
|
+
# 3PCF components
|
|
1321
|
+
conjmap = [0,1,3,2]
|
|
1322
|
+
for elm in range(4):
|
|
1323
|
+
for elphi, phi in enumerate(self.phi):
|
|
1324
|
+
N0 = 1./(2*np.pi) * self.npcf_multipoles_norm[0].astype(complex)
|
|
1325
|
+
tmp = 1./(2*np.pi) * self.npcf_multipoles[elm,0].astype(complex)
|
|
1326
|
+
for n in range(1,self.nmax+1):
|
|
1327
|
+
_const = 1./(2*np.pi) * np.exp(1J*n*phi)
|
|
1328
|
+
tmp += _const * self.npcf_multipoles[elm,n].astype(complex)
|
|
1329
|
+
tmp += _const.conj() * self.npcf_multipoles[conjmap[elm],n][ztiler].astype(complex).transpose(0,2,1)
|
|
1330
|
+
self.npcf[elm,...,elphi] = tmp
|
|
1331
|
+
# Number of triangles
|
|
1332
|
+
for elphi, phi in enumerate(self.phi):
|
|
1333
|
+
tmptotnorm = 1./(2*np.pi) * self.npcf_multipoles_norm[0].astype(complex)
|
|
1334
|
+
for n in range(1,self.nmax+1):
|
|
1335
|
+
_const = 1./(2*np.pi) * np.exp(1J*n*phi)
|
|
1336
|
+
tmptotnorm += _const * self.npcf_multipoles_norm[n].astype(complex)
|
|
1337
|
+
tmptotnorm += _const.conj() * self.npcf_multipoles_norm[n][ztiler].astype(complex).transpose(0,2,1)
|
|
1338
|
+
self.npcf_norm[...,elphi] = tmptotnorm
|
|
1339
|
+
|
|
1340
|
+
if self.is_edge_corrected:
|
|
1341
|
+
dphi = self.phi[1] - self.phi[0]
|
|
1342
|
+
N0 = dphi/(2*np.pi) * self.npcf_multipoles_norm[self.nmax].astype(complex)
|
|
1343
|
+
sel_zero = np.isnan(N0)
|
|
1344
|
+
_a = self.npcf
|
|
1345
|
+
_b = N0.real[np.newaxis, :, :, :, np.newaxis]
|
|
1346
|
+
self.npcf = np.divide(_a, _b, out=np.zeros_like(_a), where=_b>0)
|
|
1347
|
+
else:
|
|
1348
|
+
_a = self.npcf
|
|
1349
|
+
_b = self.npcf_norm
|
|
1350
|
+
self.npcf = np.divide(_a, _b, out=np.zeros_like(_a), where=_b>0)
|
|
1351
|
+
self.projection = "X"
|
|
1352
|
+
|
|
1353
|
+
def multipoles2npcf(self, projection='Centroid'):
|
|
1354
|
+
r"""
|
|
1355
|
+
Notes
|
|
1356
|
+
-----
|
|
1357
|
+
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.
|
|
1358
|
+
"""
|
|
1359
|
+
assert(projection in self.projections_avail)
|
|
1360
|
+
int_projection = {'X':0,'Centroid':1}
|
|
1361
|
+
_, nzcombis, rbins, rbins = np.shape(self.npcf_multipoles[0])
|
|
1362
|
+
thisnpcf = np.zeros(4*self.nbinsz*self.nbinsz*self.nbinsz*self.nbinsr*self.nbinsr*len(self.phi), dtype=np.complex128)
|
|
1363
|
+
thisnpcf_norm = np.zeros(self.nbinsz*self.nbinsz*self.nbinsz*self.nbinsr*self.nbinsr*len(self.phi), dtype=np.complex128)
|
|
1364
|
+
self.clib.multipoles2npcf_ggg(
|
|
1365
|
+
self.npcf_multipoles.flatten(), self.npcf_multipoles_norm.flatten(), np.int32(self.nmax), np.int32(self.nbinsz),
|
|
1366
|
+
self.bin_centers_mean, np.int32(self.nbinsr), self.phi.astype(np.float64), np.int32(self.nbinsphi[0]),
|
|
1367
|
+
np.int32(int_projection[projection]), np.int32(self.nthreads), thisnpcf, thisnpcf_norm)
|
|
1368
|
+
self.npcf = thisnpcf.reshape((4,nzcombis,self.nbinsr,self.nbinsr,len(self.phi)))
|
|
1369
|
+
self.npcf_norm = thisnpcf_norm.reshape((nzcombis,self.nbinsr,self.nbinsr,len(self.phi)))
|
|
1370
|
+
self.projection = projection
|
|
1371
|
+
|
|
1372
|
+
## PROJECTIONS (Preferably use direct in c-level) ##
|
|
1373
|
+
def projectnpcf(self, projection):
|
|
1374
|
+
super()._projectnpcf(self, projection)
|
|
1375
|
+
|
|
1376
|
+
def _x2centroid(self):
|
|
1377
|
+
gammas_cen = np.zeros_like(self.npcf)
|
|
1378
|
+
pimod = lambda x: x%(2*np.pi) - 2*np.pi*(x%(2*np.pi)>=np.pi)
|
|
1379
|
+
npcf_cen = np.zeros(self.npcf.shape, dtype=complex)
|
|
1380
|
+
_centers = np.mean(self.bin_centers, axis=0)
|
|
1381
|
+
for elb1, bin1 in enumerate(_centers):
|
|
1382
|
+
for elb2, bin2 in enumerate(_centers):
|
|
1383
|
+
bin3 = np.sqrt(bin1**2 + bin2**2 - 2*bin1*bin2*np.cos(self.phi))
|
|
1384
|
+
phiexp = np.exp(1J*self.phi)
|
|
1385
|
+
phiexp_c = np.exp(-1J*self.phi)
|
|
1386
|
+
prod1 = (bin1 + bin2*phiexp_c)/(bin1 + bin2*phiexp) #q1
|
|
1387
|
+
prod2 = (2*bin1 - bin2*phiexp_c)/(2*bin1 - bin2*phiexp) #q2
|
|
1388
|
+
prod3 = (2*bin2*phiexp_c - bin1)/(2*bin2*phiexp - bin1) #q3
|
|
1389
|
+
prod1_inv = prod1.conj()/np.abs(prod1)
|
|
1390
|
+
prod2_inv = prod2.conj()/np.abs(prod2)
|
|
1391
|
+
prod3_inv = prod3.conj()/np.abs(prod3)
|
|
1392
|
+
rot_nom = np.zeros((4,len(self.phi)))
|
|
1393
|
+
rot_nom[0] = pimod(np.angle(prod1*prod2*prod3*np.exp(3*1J*self.phi)))
|
|
1394
|
+
rot_nom[1] = pimod(np.angle(prod1_inv*prod2*prod3*np.exp(1J*self.phi)))
|
|
1395
|
+
rot_nom[2] = pimod(np.angle(prod1*prod2_inv*prod3*np.exp(3*1J*self.phi)))
|
|
1396
|
+
rot_nom[3] = pimod(np.angle(prod1*prod2*prod3_inv*np.exp(-1J*self.phi)))
|
|
1397
|
+
gammas_cen[:,:,elb1,elb2] = self.npcf[:,:,elb1,elb2]*np.exp(1j*rot_nom)[:,np.newaxis,:]
|
|
1398
|
+
return gammas_cen
|
|
1399
|
+
|
|
1400
|
+
def computeMap3(self, radii, do_multiscale=False, tofile=False, filtercache=None):
|
|
1401
|
+
"""
|
|
1402
|
+
Compute third-order aperture statistics using the polynomial filter.
|
|
1403
|
+
"""
|
|
1404
|
+
|
|
1405
|
+
if self.npcf is None and self.npcf_multipoles is not None:
|
|
1406
|
+
self.multipoles2npcf(projection='Centroid')
|
|
1407
|
+
|
|
1408
|
+
if self.projection != "Centroid":
|
|
1409
|
+
self.projectnpcf("Centroid")
|
|
1410
|
+
|
|
1411
|
+
nradii = len(radii)
|
|
1412
|
+
if not do_multiscale:
|
|
1413
|
+
nrcombis = nradii
|
|
1414
|
+
filterfunc = self._map3_filtergrid_singleR
|
|
1415
|
+
_rcut = 1
|
|
1416
|
+
else:
|
|
1417
|
+
nrcombis = nradii*nradii*nradii
|
|
1418
|
+
filterfunc = self._map3_filtergrid_multiR
|
|
1419
|
+
_rcut = nradii
|
|
1420
|
+
map3s = np.zeros((8, self.nzcombis, nrcombis), dtype=complex)
|
|
1421
|
+
M3 = np.zeros((self.nzcombis, nrcombis), dtype=complex)
|
|
1422
|
+
M2M1 = np.zeros((self.nzcombis, nrcombis), dtype=complex)
|
|
1423
|
+
M2M2 = np.zeros((self.nzcombis, nrcombis), dtype=complex)
|
|
1424
|
+
M2M3 = np.zeros((self.nzcombis, nrcombis), dtype=complex)
|
|
1425
|
+
tmprcombi = 0
|
|
1426
|
+
|
|
1427
|
+
for elr1, R1 in enumerate(radii):
|
|
1428
|
+
for elr2, R2 in enumerate(radii[:_rcut]):
|
|
1429
|
+
for elr3, R3 in enumerate(radii[:_rcut]):
|
|
1430
|
+
if not do_multiscale:
|
|
1431
|
+
R2 = R1
|
|
1432
|
+
R3 = R1
|
|
1433
|
+
if filtercache is not None:
|
|
1434
|
+
T0, T3_123, T3_231, T3_312 = filtercache[tmprcombi][0], filtercache[tmprcombi][1], filtercache[tmprcombi][2], filtercache[tmprcombi][3]
|
|
1435
|
+
else:
|
|
1436
|
+
T0, T3_123, T3_231, T3_312 = filterfunc(R1, R2, R3)
|
|
1437
|
+
M3[:,tmprcombi] = np.nansum(T0*self.npcf[0,...],axis=(1,2,3))
|
|
1438
|
+
M2M1[:,tmprcombi] = np.nansum(T3_123*self.npcf[1,...],axis=(1,2,3))
|
|
1439
|
+
M2M2[:,tmprcombi] = np.nansum(T3_231*self.npcf[2,...],axis=(1,2,3))
|
|
1440
|
+
M2M3[:,tmprcombi] = np.nansum(T3_312*self.npcf[3,...],axis=(1,2,3))
|
|
1441
|
+
tmprcombi += 1
|
|
1442
|
+
map3s[0] = 1./4. * (+M2M1+M2M2+M2M3 + M3).real # MapMapMap
|
|
1443
|
+
map3s[1] = 1./4. * (+M2M1+M2M2-M2M3 + M3).imag # MapMapMx
|
|
1444
|
+
map3s[2] = 1./4. * (+M2M1-M2M2+M2M3 + M3).imag # MapMxMap
|
|
1445
|
+
map3s[3] = 1./4. * (-M2M1+M2M2+M2M3 + M3).imag # MxMapMap
|
|
1446
|
+
map3s[4] = 1./4. * (-M2M1+M2M2+M2M3 - M3).real # MapMxMx
|
|
1447
|
+
map3s[5] = 1./4. * (+M2M1-M2M2+M2M3 - M3).real # MxMapMx
|
|
1448
|
+
map3s[6] = 1./4. * (+M2M1+M2M2-M2M3 - M3).real # MxMxMap
|
|
1449
|
+
map3s[7] = 1./4. * (+M2M1+M2M2+M2M3 - M3).imag # MxMxMx
|
|
1450
|
+
|
|
1451
|
+
if tofile:
|
|
1452
|
+
# Write to file
|
|
1453
|
+
pass
|
|
1454
|
+
|
|
1455
|
+
return map3s
|
|
1456
|
+
|
|
1457
|
+
def _map3_filtergrid_singleR(self, R1, R2, R3):
|
|
1458
|
+
return self.__map3_filtergrid_singleR(R1, R2, R3, self.bin_edges, self.bin_centers_mean, self.phi)
|
|
1459
|
+
|
|
1460
|
+
@staticmethod
|
|
1461
|
+
@jit(nopython=True)
|
|
1462
|
+
def __map3_filtergrid_singleR(R1, R2, R3, normys_edges, normys_centers, phis):
|
|
1463
|
+
|
|
1464
|
+
# To avoid zero divisions we set some default bin centers for the evaluation of the filter
|
|
1465
|
+
# As for those positions the 3pcf is zero those will not contribute to the map3 integral
|
|
1466
|
+
if (np.min(normys_centers)==0):
|
|
1467
|
+
_sel = normys_centers!=0
|
|
1468
|
+
_avratios = np.mean(normys_centers[_sel]/normys_edges[_sel])
|
|
1469
|
+
normys_centers[~_sel] = _avratios*normys_edges[~_sel]
|
|
1470
|
+
|
|
1471
|
+
R_ap = R1
|
|
1472
|
+
nbinsr = len(normys_centers)
|
|
1473
|
+
nbinsphi = len(phis)
|
|
1474
|
+
_cphis = np.cos(phis)
|
|
1475
|
+
_c2phis = np.cos(2*phis)
|
|
1476
|
+
_sphis = np.sin(phis)
|
|
1477
|
+
_ephis = np.e**(1J*phis)
|
|
1478
|
+
_ephisc = np.e**(-1J*phis)
|
|
1479
|
+
_e2phis = np.e**(2J*phis)
|
|
1480
|
+
_e2phisc = np.e**(-2J*phis)
|
|
1481
|
+
T0 = np.zeros((nbinsr, nbinsr, nbinsphi), dtype=nb_complex128)
|
|
1482
|
+
T3_123 = np.zeros((nbinsr, nbinsr, nbinsphi), dtype=nb_complex128)
|
|
1483
|
+
T3_231 = np.zeros((nbinsr, nbinsr, nbinsphi), dtype=nb_complex128)
|
|
1484
|
+
T3_312 = np.zeros((nbinsr, nbinsr, nbinsphi), dtype=nb_complex128)
|
|
1485
|
+
for elb1 in range(nbinsr):
|
|
1486
|
+
_y1 = normys_centers[elb1]
|
|
1487
|
+
_dbin1 = normys_edges[elb1+1] - normys_edges[elb1]
|
|
1488
|
+
for elb2 in range(nbinsr):
|
|
1489
|
+
_y2 = normys_centers[elb2]
|
|
1490
|
+
_y14 = _y1**4
|
|
1491
|
+
_y13y2 = _y1**3*_y2
|
|
1492
|
+
_y12y22 = _y1**2*_y2**2
|
|
1493
|
+
_y1y23 = _y1*_y2**3
|
|
1494
|
+
_y24 = _y2**4
|
|
1495
|
+
_dbin2 = normys_edges[elb2+1] - normys_edges[elb2]
|
|
1496
|
+
_dbinphi = phis[1] - phis[0]
|
|
1497
|
+
_absq1s = 1./9.*(4*_y1**2 - 4*_y1*_y2*_cphis + 1*_y2**2)
|
|
1498
|
+
_absq2s = 1./9.*(1*_y1**2 - 4*_y1*_y2*_cphis + 4*_y2**2)
|
|
1499
|
+
_absq3s = 1./9.*(1*_y1**2 + 2*_y1*_y2*_cphis + 1*_y2**2)
|
|
1500
|
+
_absq123s = 2./3. * (_y1**2+_y2**2-_y1*_y2*_cphis)
|
|
1501
|
+
_absq1q2q3_2 = _absq1s*_absq2s*_absq3s
|
|
1502
|
+
_measures = _y1*_dbin1/R_ap**2 * _y2*_dbin2/R_ap**2 * _dbinphi/(2*np.pi)
|
|
1503
|
+
nextT0 = _absq1q2q3_2/R_ap**6 * np.e**(-_absq123s/(2*R_ap**2))
|
|
1504
|
+
T0[elb1,elb2] = 1./24. * _measures * nextT0
|
|
1505
|
+
_tmp1 = _y1**4 + _y2**4 + _y1**2*_y2**2 * (2*np.cos(2*phis)-5.)
|
|
1506
|
+
_tmp2 = (_y1**2+_y2**2)*_cphis + 9J*(_y1**2-_y2**2)*_sphis
|
|
1507
|
+
q1q2q3starsq = -1./81*(2*_tmp1 - _y1*_y2*_tmp2)
|
|
1508
|
+
nextT3_123 = np.e**(-_absq123s/(2*R_ap**2)) * (1./24*_absq1q2q3_2/R_ap**6 -
|
|
1509
|
+
1./9.*q1q2q3starsq/R_ap**4 +
|
|
1510
|
+
1./27*(q1q2q3starsq**2/(_absq1q2q3_2*R_ap**2) +
|
|
1511
|
+
2*q1q2q3starsq/(_absq3s*R_ap**2)))
|
|
1512
|
+
_231inner = -4*_y14 + 2*_y24 + _y13y2*8*_cphis + _y12y22*(8*_e2phis-4-_e2phisc) + _y1y23*(_ephisc-8*_ephis)
|
|
1513
|
+
q2q3q1starsq = -1./81*(_231inner)
|
|
1514
|
+
nextT3_231 = np.e**(-_absq123s/(2*R_ap**2)) * (1./24*_absq1q2q3_2/R_ap**6 -
|
|
1515
|
+
1./9.*q2q3q1starsq/R_ap**4 +
|
|
1516
|
+
1./27*(q2q3q1starsq**2/(_absq1q2q3_2*R_ap**2) +
|
|
1517
|
+
2*q2q3q1starsq/(_absq1s*R_ap**2)))
|
|
1518
|
+
_312inner = 2*_y14 - 4*_y24 - _y13y2*(8*_ephisc-_ephis) - _y12y22*(4+_e2phis-8*_e2phisc) + 8*_y1y23*_cphis
|
|
1519
|
+
q3q1q2starsq = -1./81*(_312inner)
|
|
1520
|
+
nextT3_312 = np.e**(-_absq123s/(2*R_ap**2)) * (1./24*_absq1q2q3_2/R_ap**6 -
|
|
1521
|
+
1./9.*q3q1q2starsq/R_ap**4 +
|
|
1522
|
+
1./27*(q3q1q2starsq**2/(_absq1q2q3_2*R_ap**2) +
|
|
1523
|
+
2*q3q1q2starsq/(_absq2s*R_ap**2)))
|
|
1524
|
+
T3_123[elb1,elb2] = _measures * nextT3_123
|
|
1525
|
+
T3_231[elb1,elb2] = _measures * nextT3_231
|
|
1526
|
+
T3_312[elb1,elb2] = _measures * nextT3_312
|
|
1527
|
+
|
|
1528
|
+
return T0, T3_123, T3_231, T3_312
|
|
1529
|
+
|
|
1530
|
+
def _map3_filtergrid_multiR(self, R1, R2, R3):
|
|
1531
|
+
return self.__map3_filtergrid_multiR(R1, R2, R3, self.bin_edges, self.bin_centers_mean, self.phi, include_measure=True)
|
|
1532
|
+
|
|
1533
|
+
@staticmethod
|
|
1534
|
+
@jit(nopython=True)
|
|
1535
|
+
def __map3_filtergrid_multiR(R1, R2, R3, normys_edges, normys_centers, phis, include_measure=True):
|
|
1536
|
+
|
|
1537
|
+
# To avoid zero divisions we set some default bin centers for the evaluation of the filter
|
|
1538
|
+
# As for those positions the 3pcf is zero those will not contribute to the map3 integral
|
|
1539
|
+
if (np.min(normys_centers)==0):
|
|
1540
|
+
_sel = normys_centers!=0
|
|
1541
|
+
_avratios = np.mean(normys_centers[_sel]/normys_edges[_sel])
|
|
1542
|
+
normys_centers[~_sel] = _avratios*normys_edges[~_sel]
|
|
1543
|
+
|
|
1544
|
+
nbinsr = len(normys_centers)
|
|
1545
|
+
nbinsphi = len(phis)
|
|
1546
|
+
_cphis = np.cos(phis)
|
|
1547
|
+
_c2phis = np.cos(2*phis)
|
|
1548
|
+
_sphis = np.sin(phis)
|
|
1549
|
+
_ephis = np.e**(1J*phis)
|
|
1550
|
+
_ephisc = np.e**(-1J*phis)
|
|
1551
|
+
_e2phis = np.e**(2J*phis)
|
|
1552
|
+
_e2phisc = np.e**(-2J*phis)
|
|
1553
|
+
T0 = np.zeros((nbinsr, nbinsr, nbinsphi), dtype=nb_complex128)
|
|
1554
|
+
T3_123 = np.zeros((nbinsr, nbinsr, nbinsphi), dtype=nb_complex128)
|
|
1555
|
+
T3_231 = np.zeros((nbinsr, nbinsr, nbinsphi), dtype=nb_complex128)
|
|
1556
|
+
T3_312 = np.zeros((nbinsr, nbinsr, nbinsphi), dtype=nb_complex128)
|
|
1557
|
+
for elb1 in range(nbinsr):
|
|
1558
|
+
_y1 = normys_centers[elb1]
|
|
1559
|
+
_dbin1 = normys_edges[elb1+1] - normys_edges[elb1]
|
|
1560
|
+
for elb2 in range(nbinsr):
|
|
1561
|
+
Theta2 = np.sqrt((R1**2*R2**2 + R1**2*R3**2 + R2**2*R3**2)/3)
|
|
1562
|
+
S = R1**2*R2**2*R3**2/Theta2**3
|
|
1563
|
+
|
|
1564
|
+
_y2 = normys_centers[elb2]
|
|
1565
|
+
_y14 = _y1**4
|
|
1566
|
+
_y13y2 = _y1**3*_y2
|
|
1567
|
+
_y12y22 = _y1**2*_y2**2
|
|
1568
|
+
_y1y23 = _y1*_y2**3
|
|
1569
|
+
_y24 = _y2**4
|
|
1570
|
+
_dbin2 = normys_edges[elb2+1] - normys_edges[elb2]
|
|
1571
|
+
_dbinphi = phis[1] - phis[0]
|
|
1572
|
+
_absq1s = 1./9.*(4*_y1**2 - 4*_y1*_y2*_cphis + 1*_y2**2)
|
|
1573
|
+
_absq2s = 1./9.*(1*_y1**2 - 4*_y1*_y2*_cphis + 4*_y2**2)
|
|
1574
|
+
_absq3s = 1./9.*(1*_y1**2 + 2*_y1*_y2*_cphis + 1*_y2**2)
|
|
1575
|
+
_absq123s = 2./3. * (_y1**2+_y2**2-_y1*_y2*_cphis)
|
|
1576
|
+
_absq1q2q3_2 = _absq1s*_absq2s*_absq3s
|
|
1577
|
+
|
|
1578
|
+
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)
|
|
1579
|
+
_frac231c = 1./3.*_y2*(2*_y1*_ephis-_y2)/_absq1s
|
|
1580
|
+
_frac312c = 1./3.*_y1*(_y1-2*_y2*_ephisc)/_absq2s
|
|
1581
|
+
_frac123c = 1./3.*(_y2**2-_y1**2+2J*_y1*_y2*_sphis)/_absq3s
|
|
1582
|
+
f1 = (R2**2+R3**2)/(2*Theta2) + _frac231c * (R2**2-R3**2)/(6*Theta2)
|
|
1583
|
+
f2 = (R1**2+R3**2)/(2*Theta2) + _frac312c * (R3**2-R1**2)/(6*Theta2)
|
|
1584
|
+
f3 = (R1**2+R2**2)/(2*Theta2) + _frac123c * (R1**2-R2**2)/(6*Theta2)
|
|
1585
|
+
f1c = f1.conj()
|
|
1586
|
+
f2c = f2.conj()
|
|
1587
|
+
f3c = f3.conj()
|
|
1588
|
+
g1c = (R2**2*R3**2/Theta2**2 + R1**2*(R3**2-R2**2)/(3*Theta2**2)*_frac231c).conj()
|
|
1589
|
+
g2c = (R3**2*R1**2/Theta2**2 + R2**2*(R1**2-R3**2)/(3*Theta2**2)*_frac312c).conj()
|
|
1590
|
+
g3c = (R1**2*R2**2/Theta2**2 + R3**2*(R2**2-R1**2)/(3*Theta2**2)*_frac123c).conj()
|
|
1591
|
+
_measures = _y1*_dbin1/Theta2 * _y2*_dbin2/Theta2 * _dbinphi/(2*np.pi)
|
|
1592
|
+
if not include_measure:
|
|
1593
|
+
_measures/=_measures
|
|
1594
|
+
nextT0 = _absq1q2q3_2/Theta2**3 * f1c**2*f2c**2*f3c**2 * np.e**(-Z)
|
|
1595
|
+
T0[elb1,elb2] = S/24. * _measures * nextT0
|
|
1596
|
+
|
|
1597
|
+
_tmp1 = _y1**4 + _y2**4 + _y1**2*_y2**2 * (2*np.cos(2*phis)-5.)
|
|
1598
|
+
_tmp2 = (_y1**2+_y2**2)*_cphis + 9J*(_y1**2-_y2**2)*_sphis
|
|
1599
|
+
q1q2q3starsq = -1./81*(2*_tmp1 - _y1*_y2*_tmp2)
|
|
1600
|
+
nextT3_123 = np.e**(-Z) * (1./24*_absq1q2q3_2/Theta2**3 * f1c**2*f2c**2*f3**2 -
|
|
1601
|
+
1./9.*q1q2q3starsq/Theta2**2 * f1c*f2c*f3*g3c +
|
|
1602
|
+
1./27*(q1q2q3starsq**2/(_absq1q2q3_2*Theta2) * g3c**2 +
|
|
1603
|
+
2*R1**2*R2**2/Theta2**2 * q1q2q3starsq/(_absq3s*Theta2) * f1c*f2c))
|
|
1604
|
+
_231inner = -4*_y14 + 2*_y24 + _y13y2*8*_cphis + _y12y22*(8*_e2phis-4-_e2phisc) + _y1y23*(_ephisc-8*_ephis)
|
|
1605
|
+
q2q3q1starsq = -1./81*(_231inner)
|
|
1606
|
+
nextT3_231 = np.e**(-Z) * (1./24*_absq1q2q3_2/Theta2**3 * f2c**2*f3c**2*f1**2 -
|
|
1607
|
+
1./9.*q2q3q1starsq/Theta2**2 * f2c*f3c*f1*g1c +
|
|
1608
|
+
1./27*(q2q3q1starsq**2/(_absq1q2q3_2*Theta2) * g1c**2 +
|
|
1609
|
+
2*R2**2*R3**2/Theta2**2 * q2q3q1starsq/(_absq1s*Theta2) * f2c*f3c))
|
|
1610
|
+
_312inner = 2*_y14 - 4*_y24 - _y13y2*(8*_ephisc-_ephis) - _y12y22*(4+_e2phis-8*_e2phisc) + 8*_y1y23*_cphis
|
|
1611
|
+
q3q1q2starsq = -1./81*(_312inner)
|
|
1612
|
+
nextT3_312 = np.e**(-Z) * (1./24*_absq1q2q3_2/Theta2**3 * f3c**2*f1c**2*f2**2 -
|
|
1613
|
+
1./9.*q3q1q2starsq/Theta2**2 * f3c*f1c*f2*g2c +
|
|
1614
|
+
1./27*(q3q1q2starsq**2/(_absq1q2q3_2*Theta2) * g2c**2 +
|
|
1615
|
+
2*R3**2*R1**2/Theta2**2 * q3q1q2starsq/(_absq2s*Theta2) * f3c*f1c))
|
|
1616
|
+
|
|
1617
|
+
T3_123[elb1,elb2] = S * _measures * nextT3_123
|
|
1618
|
+
T3_231[elb1,elb2] = S * _measures * nextT3_231
|
|
1619
|
+
T3_312[elb1,elb2] = S * _measures * nextT3_312
|
|
1620
|
+
|
|
1621
|
+
return T0, T3_123, T3_231, T3_312
|
|
1622
|
+
|
|
1623
|
+
|
|
1624
|
+
class GNNCorrelation(BinnedNPCF):
|
|
1625
|
+
r""" Class containing methods to measure and and obtain statistics that are built
|
|
1626
|
+
from third-order source-lens-lens (G3L) correlation functions.
|
|
1627
|
+
|
|
1628
|
+
Attributes
|
|
1629
|
+
----------
|
|
1630
|
+
min_sep: float
|
|
1631
|
+
The smallest distance of each vertex for which the NPCF is computed.
|
|
1632
|
+
max_sep: float
|
|
1633
|
+
The largest distance of each vertex for which the NPCF is computed.
|
|
1634
|
+
zweighting: bool
|
|
1635
|
+
Has no effect at the moment
|
|
1636
|
+
zweighting_sigma: bool
|
|
1637
|
+
Has not effect at the moment
|
|
1638
|
+
|
|
1639
|
+
Notes
|
|
1640
|
+
-----
|
|
1641
|
+
Inherits all other parameters and attributes from :class:`BinnedNPCF`.
|
|
1642
|
+
Additional child-specific parameters can be passed via ``kwargs``.
|
|
1643
|
+
Either ``nbinsr`` or ``binsize`` has to be provided to fix the binning scheme .
|
|
1644
|
+
"""
|
|
1645
|
+
|
|
1646
|
+
def __init__(self, min_sep, max_sep, zweighting=False, zweighting_sigma=None, **kwargs):
|
|
1647
|
+
super().__init__(3, [2,0,0], n_cfs=1, min_sep=min_sep, max_sep=max_sep, **kwargs)
|
|
1648
|
+
self.nmax = self.nmaxs[0]
|
|
1649
|
+
self.phi = self.phis[0]
|
|
1650
|
+
self.projection = None
|
|
1651
|
+
self.projections_avail = [None, "X"]
|
|
1652
|
+
self.nbinsz_source = None
|
|
1653
|
+
self.nbinsz_lens = None
|
|
1654
|
+
|
|
1655
|
+
assert(zweighting in [True, False])
|
|
1656
|
+
self.zweighting = zweighting
|
|
1657
|
+
self.zweighting_sigma = zweighting_sigma
|
|
1658
|
+
if not self.zweighting :
|
|
1659
|
+
self.zweighting_sigma = None
|
|
1660
|
+
else:
|
|
1661
|
+
assert(isinstance(self.zweighting_sigma, float))
|
|
1662
|
+
|
|
1663
|
+
# (Add here any newly implemented projections)
|
|
1664
|
+
self._initprojections(self)
|
|
1665
|
+
|
|
1666
|
+
|
|
1667
|
+
def __process_patches(self, cat_source, cat_lens, dotomo_source=True, dotomo_lens=True, rotsignflip=False,
|
|
1668
|
+
apply_edge_correction=False, save_patchres=False, save_filebase="", keep_patchres=False):
|
|
1669
|
+
if save_patchres:
|
|
1670
|
+
if not Path(save_patchres).is_dir():
|
|
1671
|
+
raise ValueError('Path to directory does not exist.')
|
|
1672
|
+
|
|
1673
|
+
for elp in range(cat_source.npatches):
|
|
1674
|
+
if self._verbose_python:
|
|
1675
|
+
print('Doing patch %i/%i'%(elp+1,cat_source.npatches))
|
|
1676
|
+
# Compute statistics on patch
|
|
1677
|
+
pscat = cat_source.frompatchind(elp,rotsignflip=rotsignflip)
|
|
1678
|
+
plcat = cat_lens.frompatchind(elp,rotsignflip=rotsignflip)
|
|
1679
|
+
pcorr = GNNCorrelation(
|
|
1680
|
+
min_sep=self.min_sep,
|
|
1681
|
+
max_sep=self.max_sep,
|
|
1682
|
+
nbinsr=self.nbinsr,
|
|
1683
|
+
nbinsphi=self.nbinsphi,
|
|
1684
|
+
nmaxs=self.nmaxs,
|
|
1685
|
+
method=self.method,
|
|
1686
|
+
multicountcorr=self.multicountcorr,
|
|
1687
|
+
shuffle_pix=self.shuffle_pix,
|
|
1688
|
+
tree_resos=self.tree_resos,
|
|
1689
|
+
rmin_pixsize=self.rmin_pixsize,
|
|
1690
|
+
resoshift_leafs=self.resoshift_leafs,
|
|
1691
|
+
minresoind_leaf=self.minresoind_leaf,
|
|
1692
|
+
maxresoind_leaf=self.maxresoind_leaf,
|
|
1693
|
+
nthreads=self.nthreads,
|
|
1694
|
+
verbosity=self.verbosity)
|
|
1695
|
+
pcorr.process(pscat, plcat, dotomo_source=dotomo_source, dotomo_lens=dotomo_lens)
|
|
1696
|
+
|
|
1697
|
+
# Update the total measurement
|
|
1698
|
+
if elp == 0:
|
|
1699
|
+
self.nbinsz_source = pcorr.nbinsz_source
|
|
1700
|
+
self.nbinsz_lens = pcorr.nbinsz_lens
|
|
1701
|
+
self.bin_centers = np.zeros_like(pcorr.bin_centers)
|
|
1702
|
+
self.npcf_multipoles = np.zeros_like(pcorr.npcf_multipoles)
|
|
1703
|
+
self.npcf_multipoles_norm = np.zeros_like(pcorr.npcf_multipoles_norm)
|
|
1704
|
+
_footnorm = np.zeros_like(pcorr.bin_centers)
|
|
1705
|
+
if keep_patchres:
|
|
1706
|
+
centers_patches = np.zeros((cat_source.npatches, *pcorr.bin_centers.shape), dtype=pcorr.bin_centers.dtype)
|
|
1707
|
+
npcf_multipoles_patches = np.zeros((cat_source.npatches, *pcorr.npcf_multipoles.shape), dtype=pcorr.npcf_multipoles.dtype)
|
|
1708
|
+
npcf_multipoles_norm_patches = np.zeros((cat_source.npatches, *pcorr.npcf_multipoles_norm.shape), dtype=pcorr.npcf_multipoles_norm.dtype)
|
|
1709
|
+
_shelltriplets = np.array([[[pcorr.npcf_multipoles_norm[0,zs*self.nbinsz_lens*self.nbinsz_lens+zl*self.nbinsz_lens+zl,i,i].real
|
|
1710
|
+
for i in range(pcorr.nbinsr)] for zl in range(self.nbinsz_lens)] for zs in range(self.nbinsz_source)])
|
|
1711
|
+
# Rough estimate of scaling of pair counts based on zeroth multipole of triplets. Note that we might get nans here due to numerical
|
|
1712
|
+
# inaccuracies in the multiple counting corrections for bins with zero triplets, so we force those values to be zero.
|
|
1713
|
+
_patchnorm = np.nan_to_num(np.sqrt(_shelltriplets))
|
|
1714
|
+
self.bin_centers += _patchnorm*pcorr.bin_centers
|
|
1715
|
+
_footnorm += _patchnorm
|
|
1716
|
+
self.npcf_multipoles += pcorr.npcf_multipoles
|
|
1717
|
+
self.npcf_multipoles_norm += pcorr.npcf_multipoles_norm
|
|
1718
|
+
if keep_patchres:
|
|
1719
|
+
centers_patches[elp] += pcorr.bin_centers
|
|
1720
|
+
npcf_multipoles_patches[elp] += pcorr.npcf_multipoles
|
|
1721
|
+
npcf_multipoles_norm_patches[elp] += pcorr.npcf_multipoles_norm
|
|
1722
|
+
if save_patchres:
|
|
1723
|
+
pcorr.saveinst(save_patchres, save_filebase+'_patch%i'%elp)
|
|
1724
|
+
|
|
1725
|
+
# Finalize the measurement on the full footprint
|
|
1726
|
+
self.bin_centers = np.divide(self.bin_centers,_footnorm, out=np.zeros_like(self.bin_centers), where=_footnorm>0)
|
|
1727
|
+
self.bin_centers_mean =np.mean(self.bin_centers, axis=(0,1))
|
|
1728
|
+
self.projection = "X"
|
|
1729
|
+
|
|
1730
|
+
if keep_patchres:
|
|
1731
|
+
return centers_patches, npcf_multipoles_patches, npcf_multipoles_norm_patches
|
|
1732
|
+
|
|
1733
|
+
# TODO: Include z-weighting in estimator
|
|
1734
|
+
# * False --> No z-weighting, nothing to do
|
|
1735
|
+
# * True --> Tomographic zweighting: Use effective weight for each tomo bin combi. Do computation as tomo case with
|
|
1736
|
+
# no z-weighting and then weight in postprocessing where (zs, zl1, zl2) --> w_{zl1, zl2} * (zs)
|
|
1737
|
+
# As this could be many zbins, might want to only allow certain zcombis -- i.e. neighbouring zbins.
|
|
1738
|
+
# Functional form similar to https://arxiv.org/pdf/1909.06190.pdf
|
|
1739
|
+
# * Note that for spectroscopic catalogs we cannot do a full spectroscopic weighting as done i.e. the brute-force method
|
|
1740
|
+
# in https://arxiv.org/pdf/1909.06190.pdf, as this breaks the multipole decomposition.
|
|
1741
|
+
# * In general, think about what could be a consistent way get a good compromise between speed vs S/N. One extreme would
|
|
1742
|
+
# be just to use some broad bins and and the std within them (so 'thinner' bins have more weight). Other extreme would
|
|
1743
|
+
# be many small zbins with proper cross-weighting and maximum distance --> Becomes less efficient for more bins.
|
|
1744
|
+
def process(self, cat_source, cat_lens, dotomo_source=True, dotomo_lens=True, rotsignflip=False, apply_edge_correction=False,
|
|
1745
|
+
save_patchres=False, save_filebase="", keep_patchres=False):
|
|
1746
|
+
r"""
|
|
1747
|
+
Compute a shear-lens-lens correlation provided a source and a lens catalog.
|
|
1748
|
+
|
|
1749
|
+
Parameters
|
|
1750
|
+
----------
|
|
1751
|
+
cat_source: orpheus.SpinTracerCatalog
|
|
1752
|
+
The source catalog which is processed
|
|
1753
|
+
cat_lens: orpheus.ScalarTracerCatalog
|
|
1754
|
+
The lens catalog which is processed
|
|
1755
|
+
dotomo_source: bool
|
|
1756
|
+
Flag that decides whether the tomographic information in the source catalog should be used. Defaults to `True`.
|
|
1757
|
+
dotomo_lens: bool
|
|
1758
|
+
Flag that decides whether the tomographic information in the lens catalog should be used. Defaults to `True`.
|
|
1759
|
+
rotsignflip: bool
|
|
1760
|
+
If the shape catalog is has been decomposed in patches, choose whether the rotation angle should be flipped.
|
|
1761
|
+
For simulated data this was always ok to set to 'False`. Defaults to `False`.
|
|
1762
|
+
apply_edge_correction: bool
|
|
1763
|
+
Flag that decides how the NPCF in the real space basis is computed.
|
|
1764
|
+
* If set to `True` the computation is done via edge-correcting the GNN-multipoles
|
|
1765
|
+
* If set to `False` both GNN and NNN are transformed separately and the ratio is done in the real-space basis
|
|
1766
|
+
Defaults to `False`.
|
|
1767
|
+
save_patchres: bool or str
|
|
1768
|
+
If the shape catalog is has been decomposed in patches, flag whether to save the GG measurements on the individual patches.
|
|
1769
|
+
Note that the path needs to exist, otherwise a `ValueError` is raised. For a flat-sky catalog this parameter
|
|
1770
|
+
has no effect. Defaults to `False`
|
|
1771
|
+
save_filebase: str
|
|
1772
|
+
Base of the filenames in which the patches are saved. The full filename will be `<save_patchres>/<save_filebase>_patchxx.npz`.
|
|
1773
|
+
Only has an effect if the shape catalog consists of multiple patches and `save_patchres` is not `False`.
|
|
1774
|
+
keep_patchres: bool
|
|
1775
|
+
If the catalog consists of multiple patches, returns all measurements on the patches. Defaults to `False`.
|
|
1776
|
+
"""
|
|
1777
|
+
self._checkcats([cat_source, cat_lens, cat_lens], [2, 0, 0])
|
|
1778
|
+
|
|
1779
|
+
# Catch typical errors, i.e. incompatible catalogs or missin patch decompositions
|
|
1780
|
+
if cat_source.geometry=='spherical' and cat_source.patchinds is None:
|
|
1781
|
+
raise ValueError('Error: Spherical catalog needs to be first decomposed into patches using the Catalog._topatches method.')
|
|
1782
|
+
if cat_lens.geometry=='spherical' and cat_lens.patchinds is None:
|
|
1783
|
+
raise ValueError('Error: Spherical catalog needs to be first decomposed into patches using the Catalog._topatches method.')
|
|
1784
|
+
if cat_source.geometry != cat_lens.geometry:
|
|
1785
|
+
raise ValueError('Incompatible geometries of source catalog (%s) and lens catalog (%s).'%(
|
|
1786
|
+
cat_source.geometry,cat_lens.geometry))
|
|
1787
|
+
|
|
1788
|
+
# Catalog consist of multiple patches
|
|
1789
|
+
if (cat_source.patchinds is not None) and (cat_lens.patchinds is not None):
|
|
1790
|
+
return self.__process_patches(cat_source, cat_lens, dotomo_source=dotomo_source, dotomo_lens=dotomo_lens,
|
|
1791
|
+
rotsignflip=rotsignflip, apply_edge_correction=apply_edge_correction,
|
|
1792
|
+
save_patchres=save_patchres, save_filebase=save_filebase, keep_patchres=keep_patchres)
|
|
1793
|
+
|
|
1794
|
+
# Catalog does not consist of patches
|
|
1795
|
+
else:
|
|
1796
|
+
|
|
1797
|
+
if not dotomo_lens and self.zweighting:
|
|
1798
|
+
print("Redshift-weighting requires tomographic computation for the lenses.")
|
|
1799
|
+
dotomo_lens = True
|
|
1800
|
+
|
|
1801
|
+
if not dotomo_source:
|
|
1802
|
+
self.nbinsz_source = 1
|
|
1803
|
+
old_zbins_source = cat_source.zbins[:]
|
|
1804
|
+
cat_source.zbins = np.zeros(cat_source.ngal, dtype=np.int32)
|
|
1805
|
+
else:
|
|
1806
|
+
self.nbinsz_source = cat_source.nbinsz
|
|
1807
|
+
if not dotomo_lens:
|
|
1808
|
+
self.nbinsz_lens = 1
|
|
1809
|
+
old_zbins_lens = cat_lens.zbins[:]
|
|
1810
|
+
cat_lens.zbins = np.zeros(cat_lens.ngal, dtype=np.int32)
|
|
1811
|
+
else:
|
|
1812
|
+
self.nbinsz_lens = cat_lens.nbinsz
|
|
1813
|
+
|
|
1814
|
+
if self.zweighting:
|
|
1815
|
+
if cat_lens.zbins_mean is None:
|
|
1816
|
+
print("Redshift-weighting requires information about mean redshift in tomo bins of lens catalog")
|
|
1817
|
+
if cat_lens.zbins_std is None:
|
|
1818
|
+
print("Warning: Redshift-dispersion in tomo bins of lens catalog not given. Set to zero.")
|
|
1819
|
+
cat_lens.zbins_std = np.zeros(self.nbinsz_lens)
|
|
1820
|
+
|
|
1821
|
+
_z3combis = self.nbinsz_source*self.nbinsz_lens*self.nbinsz_lens
|
|
1822
|
+
_r2combis = self.nbinsr*self.nbinsr
|
|
1823
|
+
sc = (self.n_cfs, self.nmax+1, _z3combis, self.nbinsr, self.nbinsr)
|
|
1824
|
+
sn = (self.nmax+1, _z3combis, self.nbinsr,self.nbinsr)
|
|
1825
|
+
szr = (self.nbinsz_source, self.nbinsz_lens, self.nbinsr)
|
|
1826
|
+
bin_centers = np.zeros(reduce(operator.mul, szr)).astype(np.float64)
|
|
1827
|
+
Upsilon_n = np.zeros(reduce(operator.mul, sc)).astype(np.complex128)
|
|
1828
|
+
Norm_n = np.zeros(reduce(operator.mul, sn)).astype(np.complex128)
|
|
1829
|
+
args_sourcecat = (cat_source.isinner.astype(np.float64), cat_source.weight.astype(np.float64),
|
|
1830
|
+
cat_source.pos1.astype(np.float64), cat_source.pos2.astype(np.float64),
|
|
1831
|
+
cat_source.tracer_1.astype(np.float64), cat_source.tracer_2.astype(np.float64),
|
|
1832
|
+
cat_source.zbins.astype(np.int32), np.int32(self.nbinsz_source), np.int32(cat_source.ngal), )
|
|
1833
|
+
args_lenscat = (cat_lens.weight.astype(np.float64), cat_lens.pos1.astype(np.float64),
|
|
1834
|
+
cat_lens.pos2.astype(np.float64), cat_lens.zbins.astype(np.int32),
|
|
1835
|
+
np.int32(self.nbinsz_lens), np.int32(cat_lens.ngal), )
|
|
1836
|
+
args_basesetup = (np.int32(self.nmax), np.float64(self.min_sep), np.float64(self.max_sep),
|
|
1837
|
+
np.int32(self.nbinsr), np.int32(self.multicountcorr), )
|
|
1838
|
+
if self.method=="Discrete":
|
|
1839
|
+
hash_dpix = max(1.,self.max_sep//10.)
|
|
1840
|
+
jointextent = list(cat_source._jointextent([cat_lens], extend=self.tree_resos[-1]))
|
|
1841
|
+
cat_source.build_spatialhash(dpix=hash_dpix, extent=jointextent)
|
|
1842
|
+
cat_lens.build_spatialhash(dpix=hash_dpix, extent=jointextent)
|
|
1843
|
+
nregions = np.int32(len(np.argwhere(cat_source.index_matcher>-1).flatten()))
|
|
1844
|
+
args_hash = (cat_source.index_matcher, cat_source.pixs_galind_bounds, cat_source.pix_gals,
|
|
1845
|
+
cat_lens.index_matcher, cat_lens.pixs_galind_bounds, cat_lens.pix_gals, nregions, )
|
|
1846
|
+
args_pixgrid = (np.float64(cat_lens.pix1_start), np.float64(cat_lens.pix1_d), np.int32(cat_lens.pix1_n),
|
|
1847
|
+
np.float64(cat_lens.pix2_start), np.float64(cat_lens.pix2_d), np.int32(cat_lens.pix2_n), )
|
|
1848
|
+
args = (*args_sourcecat,
|
|
1849
|
+
*args_lenscat,
|
|
1850
|
+
*args_basesetup,
|
|
1851
|
+
*args_hash,
|
|
1852
|
+
*args_pixgrid,
|
|
1853
|
+
np.int32(self.nthreads),
|
|
1854
|
+
np.int32(self._verbose_c),
|
|
1855
|
+
bin_centers,
|
|
1856
|
+
Upsilon_n,
|
|
1857
|
+
Norm_n, )
|
|
1858
|
+
func = self.clib.alloc_Gammans_discrete_GNN
|
|
1859
|
+
if self.method == "DoubleTree":
|
|
1860
|
+
cutfirst = np.int32(self.tree_resos[0]==0.)
|
|
1861
|
+
jointextent = list(cat_source._jointextent([cat_lens], extend=self.tree_resos[-1]))
|
|
1862
|
+
# Build multihashes for sources and lenses
|
|
1863
|
+
mhash_source = cat_source.multihash(dpixs=self.tree_resos[cutfirst:], dpix_hash=self.tree_resos[-1],
|
|
1864
|
+
shuffle=self.shuffle_pix, normed=True, extent=jointextent)
|
|
1865
|
+
sngal_resos, spos1s, spos2s, sweights, szbins, sisinners, sallfields, sindex_matchers, \
|
|
1866
|
+
spixs_galind_bounds, spix_gals, sdpixs1_true, sdpixs2_true = mhash_source
|
|
1867
|
+
ngal_resos_source = np.array(sngal_resos, dtype=np.int32)
|
|
1868
|
+
weight_resos_source = np.concatenate(sweights).astype(np.float64)
|
|
1869
|
+
pos1_resos_source = np.concatenate(spos1s).astype(np.float64)
|
|
1870
|
+
pos2_resos_source = np.concatenate(spos2s).astype(np.float64)
|
|
1871
|
+
zbin_resos_source = np.concatenate(szbins).astype(np.int32)
|
|
1872
|
+
isinner_resos_source = np.concatenate(sisinners).astype(np.float64)
|
|
1873
|
+
e1_resos_source = np.concatenate([sallfields[i][0] for i in range(len(sallfields))]).astype(np.float64)
|
|
1874
|
+
e2_resos_source = np.concatenate([sallfields[i][1] for i in range(len(sallfields))]).astype(np.float64)
|
|
1875
|
+
index_matcher_source = np.concatenate(sindex_matchers).astype(np.int32)
|
|
1876
|
+
pixs_galind_bounds_source = np.concatenate(spixs_galind_bounds).astype(np.int32)
|
|
1877
|
+
pix_gals_source = np.concatenate(spix_gals).astype(np.int32)
|
|
1878
|
+
mhash_lens = cat_lens.multihash(dpixs=self.tree_resos[cutfirst:], dpix_hash=self.tree_resos[-1],
|
|
1879
|
+
shuffle=self.shuffle_pix, normed=True, extent=jointextent)
|
|
1880
|
+
lngal_resos, lpos1s, lpos2s, lweights, lzbins, lisinners, lallfields, lindex_matchers, \
|
|
1881
|
+
lpixs_galind_bounds, lpix_gals, ldpixs1_true, ldpixs2_true = mhash_lens
|
|
1882
|
+
ngal_resos_lens = np.array(lngal_resos, dtype=np.int32)
|
|
1883
|
+
weight_resos_lens = np.concatenate(lweights).astype(np.float64)
|
|
1884
|
+
pos1_resos_lens = np.concatenate(lpos1s).astype(np.float64)
|
|
1885
|
+
pos2_resos_lens = np.concatenate(lpos2s).astype(np.float64)
|
|
1886
|
+
zbin_resos_lens = np.concatenate(lzbins).astype(np.int32)
|
|
1887
|
+
isinner_resos_lens = np.concatenate(lisinners).astype(np.float64)
|
|
1888
|
+
index_matcher_lens = np.concatenate(lindex_matchers).astype(np.int32)
|
|
1889
|
+
pixs_galind_bounds_lens = np.concatenate(lpixs_galind_bounds).astype(np.int32)
|
|
1890
|
+
pix_gals_lens = np.asarray(np.concatenate(lpix_gals)).astype(np.int32)
|
|
1891
|
+
index_matcher_flat = np.argwhere(cat_source.index_matcher>-1).flatten().astype(np.int32)
|
|
1892
|
+
nregions = np.int32(len(index_matcher_flat))
|
|
1893
|
+
# Collect args
|
|
1894
|
+
args_resoinfo = (np.int32(self.tree_nresos), np.int32(self.tree_nresos-cutfirst),
|
|
1895
|
+
sdpixs1_true.astype(np.float64), sdpixs2_true.astype(np.float64), self.tree_redges, )
|
|
1896
|
+
args_leafs = (np.int32(self.resoshift_leafs), np.int32(self.minresoind_leaf),
|
|
1897
|
+
np.int32(self.maxresoind_leaf), )
|
|
1898
|
+
args_resos = (isinner_resos_source, weight_resos_source, pos1_resos_source, pos2_resos_source,
|
|
1899
|
+
e1_resos_source, e2_resos_source, zbin_resos_source, ngal_resos_source,
|
|
1900
|
+
np.int32(self.nbinsz_source), isinner_resos_lens, weight_resos_lens, pos1_resos_lens,
|
|
1901
|
+
pos2_resos_lens, zbin_resos_lens, ngal_resos_lens, np.int32(self.nbinsz_lens), )
|
|
1902
|
+
args_mhash = (index_matcher_source, pixs_galind_bounds_source, pix_gals_source,
|
|
1903
|
+
index_matcher_lens, pixs_galind_bounds_lens, pix_gals_lens, index_matcher_flat, nregions, )
|
|
1904
|
+
args_pixgrid = (np.float64(cat_lens.pix1_start), np.float64(cat_lens.pix1_d), np.int32(cat_lens.pix1_n),
|
|
1905
|
+
np.float64(cat_lens.pix2_start), np.float64(cat_lens.pix2_d), np.int32(cat_lens.pix2_n), )
|
|
1906
|
+
args = (*args_resoinfo,
|
|
1907
|
+
*args_leafs,
|
|
1908
|
+
*args_resos,
|
|
1909
|
+
*args_basesetup,
|
|
1910
|
+
*args_mhash,
|
|
1911
|
+
*args_pixgrid,
|
|
1912
|
+
np.int32(self.nthreads),
|
|
1913
|
+
np.int32(self._verbose_c),
|
|
1914
|
+
bin_centers,
|
|
1915
|
+
Upsilon_n,
|
|
1916
|
+
Norm_n, )
|
|
1917
|
+
func = self.clib.alloc_Gammans_doubletree_GNN
|
|
1918
|
+
if self._verbose_debug:
|
|
1919
|
+
for elarg, arg in enumerate(args):
|
|
1920
|
+
toprint = (elarg, type(arg),)
|
|
1921
|
+
if isinstance(arg, np.ndarray):
|
|
1922
|
+
toprint += (type(arg[0]), arg.shape)
|
|
1923
|
+
toprint += (func.argtypes[elarg], )
|
|
1924
|
+
print(toprint)
|
|
1925
|
+
print(arg)
|
|
1926
|
+
|
|
1927
|
+
func(*args)
|
|
1928
|
+
|
|
1929
|
+
self.bin_centers = bin_centers.reshape(szr)
|
|
1930
|
+
self.bin_centers_mean = np.mean(self.bin_centers, axis=(0,1))
|
|
1931
|
+
self.npcf_multipoles = np.nan_to_num(Upsilon_n.reshape(sc))
|
|
1932
|
+
self.npcf_multipoles_norm = np.nan_to_num(Norm_n.reshape(sn))
|
|
1933
|
+
self.projection = "X"
|
|
1934
|
+
self.is_edge_corrected = False
|
|
1935
|
+
|
|
1936
|
+
if apply_edge_correction:
|
|
1937
|
+
self.edge_correction()
|
|
1938
|
+
|
|
1939
|
+
if not dotomo_source:
|
|
1940
|
+
cat_source.zbins = old_zbins_source
|
|
1941
|
+
if not dotomo_lens:
|
|
1942
|
+
cat_lens.zbins = old_zbins_lens
|
|
1943
|
+
|
|
1944
|
+
def edge_correction(self, ret_matrices=False):
|
|
1945
|
+
assert(not self.is_edge_corrected)
|
|
1946
|
+
def gen_M_matrix(thet1,thet2,threepcf_n_norm):
|
|
1947
|
+
nvals, ntheta, _ = threepcf_n_norm.shape
|
|
1948
|
+
nmax = (nvals-1)//2
|
|
1949
|
+
narr = np.arange(-nmax,nmax+1, dtype=np.int)
|
|
1950
|
+
nextM = np.zeros((nvals,nvals))
|
|
1951
|
+
for ind, ell in enumerate(narr):
|
|
1952
|
+
lminusn = ell-narr
|
|
1953
|
+
sel = np.logical_and(lminusn+nmax>=0, lminusn+nmax<nvals)
|
|
1954
|
+
nextM[ind,sel] = threepcf_n_norm[(lminusn+nmax)[sel],thet1,thet2].real / threepcf_n_norm[nmax,thet1,thet2].real
|
|
1955
|
+
return nextM
|
|
1956
|
+
|
|
1957
|
+
nvals, nzcombis, ntheta, _ = self.npcf_multipoles_norm.shape
|
|
1958
|
+
nmax = nvals-1
|
|
1959
|
+
threepcf_n_full = np.zeros((1,2*nmax+1, nzcombis, ntheta, ntheta), dtype=complex)
|
|
1960
|
+
threepcf_n_norm_full = np.zeros((2*nmax+1, nzcombis, ntheta, ntheta), dtype=complex)
|
|
1961
|
+
threepcf_n_corr = np.zeros(threepcf_n_full.shape, dtype=np.complex)
|
|
1962
|
+
threepcf_n_full[:,nmax:] = self.npcf_multipoles
|
|
1963
|
+
threepcf_n_norm_full[nmax:] = self.npcf_multipoles_norm
|
|
1964
|
+
for nextn in range(1,nvals):
|
|
1965
|
+
threepcf_n_full[0,nmax-nextn] = self.npcf_multipoles[0,nextn].transpose(0,2,1)
|
|
1966
|
+
threepcf_n_norm_full[nmax-nextn] = self.npcf_multipoles_norm[nextn].transpose(0,2,1)
|
|
1967
|
+
|
|
1968
|
+
if ret_matrices:
|
|
1969
|
+
mats = np.zeros((nzcombis,ntheta,ntheta,nvals,nvals))
|
|
1970
|
+
for indz in range(nzcombis):
|
|
1971
|
+
#sys.stdout.write("%i"%indz)
|
|
1972
|
+
for thet1 in range(ntheta):
|
|
1973
|
+
for thet2 in range(ntheta):
|
|
1974
|
+
nextM = gen_M_matrix(thet1,thet2,threepcf_n_norm_full[:,indz])
|
|
1975
|
+
nextM_inv = np.linalg.inv(nextM)
|
|
1976
|
+
if ret_matrices:
|
|
1977
|
+
mats[indz,thet1,thet2] = nextM
|
|
1978
|
+
threepcf_n_corr[0,:,indz,thet1,thet2] = np.matmul(nextM_inv,threepcf_n_full[0,:,indz,thet1,thet2])
|
|
1979
|
+
|
|
1980
|
+
self.npcf_multipoles = threepcf_n_corr[:,nmax:]
|
|
1981
|
+
self.is_edge_corrected = True
|
|
1982
|
+
|
|
1983
|
+
if ret_matrices:
|
|
1984
|
+
return threepcf_n_corr[:,nmax:], mats
|
|
1985
|
+
|
|
1986
|
+
# TODO:
|
|
1987
|
+
# * Include the z-weighting method
|
|
1988
|
+
# * Include the 2pcf as spline --> Should we also add an option to compute it here? Might be a mess
|
|
1989
|
+
# as then we also would need methods to properly distribute randoms...
|
|
1990
|
+
# * Do a voronoi-tesselation at the multipole level? Would be just 2D, but still might help? Eventually
|
|
1991
|
+
# bundle together cells s.t. tot_weight > theshold? However, this might then make the binning courser
|
|
1992
|
+
# for certain triangle configs(?)
|
|
1993
|
+
def multipoles2npcf(self):
|
|
1994
|
+
r"""
|
|
1995
|
+
Notes
|
|
1996
|
+
-----
|
|
1997
|
+
* The Upsilon and Norms are only computed for the n>0 multipoles. The n<0 multipoles are recovered by symmetry considerations, i.e.:
|
|
1998
|
+
|
|
1999
|
+
.. math::
|
|
2000
|
+
|
|
2001
|
+
\Upsilon_{-n}(\theta_1, \theta_2, z_1, z_2, z_3) =
|
|
2002
|
+
\Upsilon_{n}(\theta_2, \theta_1, z_1, z_3, z_2)
|
|
2003
|
+
|
|
2004
|
+
As the tomographic bin combinations are interpreted as a flat list, they need to be appropriately shuffled. This is handled by ``ztiler``.
|
|
2005
|
+
|
|
2006
|
+
* When dividing by the (weighted) counts ``N``, all contributions for which ``N <= 0`` are set to zero.
|
|
2007
|
+
|
|
2008
|
+
"""
|
|
2009
|
+
_, nzcombis, rbins, rbins = np.shape(self.npcf_multipoles[0])
|
|
2010
|
+
self.npcf = np.zeros((self.n_cfs, nzcombis, rbins, rbins, len(self.phi)), dtype=complex)
|
|
2011
|
+
self.npcf_norm = np.zeros((nzcombis, rbins, rbins, len(self.phi)), dtype=float)
|
|
2012
|
+
ztiler = np.arange(self.nbinsz_source*self.nbinsz_lens*self.nbinsz_lens).reshape(
|
|
2013
|
+
(self.nbinsz_source,self.nbinsz_lens,self.nbinsz_lens)).transpose(0,2,1).flatten().astype(np.int32)
|
|
2014
|
+
|
|
2015
|
+
# 3PCF components
|
|
2016
|
+
conjmap = [0]
|
|
2017
|
+
N0 = 1./(2*np.pi) * self.npcf_multipoles_norm[0].astype(complex)
|
|
2018
|
+
for elm in range(self.n_cfs):
|
|
2019
|
+
for elphi, phi in enumerate(self.phi):
|
|
2020
|
+
tmp = 1./(2*np.pi) * self.npcf_multipoles[elm,0].astype(complex)
|
|
2021
|
+
for n in range(1,self.nmax+1):
|
|
2022
|
+
_const = 1./(2*np.pi) * np.exp(1J*n*phi)
|
|
2023
|
+
tmp += _const * self.npcf_multipoles[elm,n].astype(complex)
|
|
2024
|
+
tmp += _const.conj() * self.npcf_multipoles[conjmap[elm],n][ztiler].astype(complex).transpose(0,2,1)
|
|
2025
|
+
self.npcf[elm,...,elphi] = tmp
|
|
2026
|
+
# Normalization
|
|
2027
|
+
for elphi, phi in enumerate(self.phi):
|
|
2028
|
+
tmptotnorm = 1./(2*np.pi) * self.npcf_multipoles_norm[0].astype(complex)
|
|
2029
|
+
for n in range(1,self.nmax+1):
|
|
2030
|
+
_const = 1./(2*np.pi) * np.exp(1J*n*phi)
|
|
2031
|
+
tmptotnorm += _const * self.npcf_multipoles_norm[n].astype(complex)
|
|
2032
|
+
tmptotnorm += _const.conj() * self.npcf_multipoles_norm[n][ztiler].astype(complex).transpose(0,2,1)
|
|
2033
|
+
self.npcf_norm[...,elphi] = tmptotnorm.real
|
|
2034
|
+
|
|
2035
|
+
if self.is_edge_corrected:
|
|
2036
|
+
sel_zero = np.isnan(N0)
|
|
2037
|
+
_a = self.npcf
|
|
2038
|
+
_b = N0.real[:, :, np.newaxis]
|
|
2039
|
+
self.npcf = np.divide(_a, _b, out=np.zeros_like(_a), where=np.abs(_b)>0)
|
|
2040
|
+
else:
|
|
2041
|
+
_a = self.npcf
|
|
2042
|
+
_b = self.npcf_norm
|
|
2043
|
+
self.npcf = np.divide(_a, _b, out=np.zeros_like(_a), where=np.abs(_b)>0)
|
|
2044
|
+
#self.npcf = self.npcf/self.npcf_norm[0][None, ...].astype(complex)
|
|
2045
|
+
self.projection = "X"
|
|
2046
|
+
|
|
2047
|
+
|
|
2048
|
+
## PROJECTIONS ##
|
|
2049
|
+
def projectnpcf(self, projection):
|
|
2050
|
+
super()._projectnpcf(self, projection)
|
|
2051
|
+
|
|
2052
|
+
## INTEGRATED MEASURES ##
|
|
2053
|
+
def computeNNM(self, radii, do_multiscale=False, tofile=False, filtercache=None):
|
|
2054
|
+
"""
|
|
2055
|
+
Compute third-order aperture statistics using the polyonomial filter of Crittenden 2002.
|
|
2056
|
+
"""
|
|
2057
|
+
nb_config.NUMBA_DEFAULT_NUM_THREADS = self.nthreads
|
|
2058
|
+
nb_config.NUMBA_NUM_THREADS = self.nthreads
|
|
2059
|
+
|
|
2060
|
+
if self.npcf is None and self.npcf_multipoles is not None:
|
|
2061
|
+
self.multipoles2npcf()
|
|
2062
|
+
|
|
2063
|
+
nradii = len(radii)
|
|
2064
|
+
if not do_multiscale:
|
|
2065
|
+
nrcombis = nradii
|
|
2066
|
+
_rcut = 1
|
|
2067
|
+
else:
|
|
2068
|
+
nrcombis = nradii*nradii*nradii
|
|
2069
|
+
_rcut = nradii
|
|
2070
|
+
NNM = np.zeros((1, self.nbinsz_source*self.nbinsz_lens*self.nbinsz_lens, nrcombis), dtype=complex)
|
|
2071
|
+
tmprcombi = 0
|
|
2072
|
+
for elr1, R1 in enumerate(radii):
|
|
2073
|
+
for elr2, R2 in enumerate(radii[:_rcut]):
|
|
2074
|
+
for elr3, R3 in enumerate(radii[:_rcut]):
|
|
2075
|
+
if not do_multiscale:
|
|
2076
|
+
R2 = R1
|
|
2077
|
+
R3 = R1
|
|
2078
|
+
if filtercache is not None:
|
|
2079
|
+
A_NNM = filtercache[tmprcombi]
|
|
2080
|
+
else:
|
|
2081
|
+
A_NNM = self._NNM_filtergrid(R1, R2, R3)
|
|
2082
|
+
NNM[0,:,tmprcombi] = np.nansum(A_NNM*self.npcf[0,...],axis=(1,2,3))
|
|
2083
|
+
tmprcombi += 1
|
|
2084
|
+
return NNM
|
|
2085
|
+
|
|
2086
|
+
def _NNM_filtergrid(self, R1, R2, R3):
|
|
2087
|
+
return self.__NNM_filtergrid(R1, R2, R3, self.bin_edges, self.bin_centers_mean, self.phi)
|
|
2088
|
+
|
|
2089
|
+
@staticmethod
|
|
2090
|
+
@jit(nopython=True, parallel=True)
|
|
2091
|
+
def __NNM_filtergrid(R1, R2, R3, edges, centers, phis):
|
|
2092
|
+
nbinsr = len(centers)
|
|
2093
|
+
nbinsphi = len(phis)
|
|
2094
|
+
_cphis = np.cos(phis)
|
|
2095
|
+
_ephis = np.e**(1J*phis)
|
|
2096
|
+
_ephisc = np.e**(-1J*phis)
|
|
2097
|
+
Theta4 = 1./3. * (R1**2*R2**2 + R1**2*R3**2 + R2**2*R3**2)
|
|
2098
|
+
a2 = 2./3. * R1**2*R2**2*R3**2 / Theta4
|
|
2099
|
+
ANNM = np.zeros((nbinsr,nbinsr,nbinsphi), dtype=nb_complex128)
|
|
2100
|
+
for elb in prange(nbinsr*nbinsr):
|
|
2101
|
+
elb1 = int(elb//nbinsr)
|
|
2102
|
+
elb2 = elb%nbinsr
|
|
2103
|
+
_y1 = centers[elb1]
|
|
2104
|
+
_dbin1 = edges[elb1+1] - edges[elb1]
|
|
2105
|
+
_y2 = centers[elb2]
|
|
2106
|
+
_dbin2 = edges[elb2+1] - edges[elb2]
|
|
2107
|
+
_dbinphi = phis[1] - phis[0]
|
|
2108
|
+
b0 = _y1**2/(2*R1**2)+_y2**2/(2*R2**2) - a2/4.*(
|
|
2109
|
+
_y1**2/R1**4 + 2*_y1*_y2*_cphis/(R1**2*R2**2) + _y2**2/R2**4)
|
|
2110
|
+
g1 = _y1 - a2/2. * (_y1/R1**2 + _y2*_ephisc/R2**2)
|
|
2111
|
+
g2 = _y2 - a2/2. * (_y2/R2**2 + _y1*_ephis/R1**2)
|
|
2112
|
+
g1c = g1.conj()
|
|
2113
|
+
g2c = g2.conj()
|
|
2114
|
+
F1 = 2*R1**2 - g1*g1c
|
|
2115
|
+
F2 = 2*R2**2 - g2*g2c
|
|
2116
|
+
pref = np.e**(-b0)/(72*np.pi*Theta4**2)
|
|
2117
|
+
sum1 = (g1-_y1)*(g2-_y2) * (1/a2*F1*F2 - (F1+F2) + 2*a2 + g1c*g2*_ephisc + g1*g2c*_ephis)
|
|
2118
|
+
sum2 = ((g2-_y2) + (g1-_y1)*_ephis) * (g1*(F2-2*a2) + g2*(F1-2*a2)*_ephisc)
|
|
2119
|
+
sum3 = 2*g1*g2*a2
|
|
2120
|
+
_measures = _y1*_dbin1 * _y2*_dbin2 * _dbinphi
|
|
2121
|
+
ANNM[elb1,elb2] = _measures * pref * (sum1-sum2+sum3)
|
|
2122
|
+
|
|
2123
|
+
return ANNM
|
|
2124
|
+
|
|
2125
|
+
# Very close to being a mere copy of GNN...
|
|
2126
|
+
class NGGCorrelation(BinnedNPCF):
|
|
2127
|
+
r""" Class containing methods to measure and and obtain statistics that are built
|
|
2128
|
+
from third-order lens-shear-shear correlation functions.
|
|
2129
|
+
|
|
2130
|
+
Attributes
|
|
2131
|
+
----------
|
|
2132
|
+
min_sep: float
|
|
2133
|
+
The smallest distance of each vertex for which the NPCF is computed.
|
|
2134
|
+
max_sep: float
|
|
2135
|
+
The largest distance of each vertex for which the NPCF is computed.
|
|
2136
|
+
|
|
2137
|
+
Notes
|
|
2138
|
+
-----
|
|
2139
|
+
Inherits all other parameters and attributes from :class:`BinnedNPCF`.
|
|
2140
|
+
Additional child-specific parameters can be passed via ``kwargs``.
|
|
2141
|
+
Either ``nbinsr`` or ``binsize`` has to be provided to fix the binning scheme .
|
|
2142
|
+
|
|
2143
|
+
Note that the different components of the NGG correlator are ordered as
|
|
2144
|
+
.. math::
|
|
2145
|
+
|
|
2146
|
+
\left[ \tilde{G}_-, \tilde{G}_+, \right] \ ,
|
|
2147
|
+
which is different to the usual conventions, but matches orpheus' conventions to
|
|
2148
|
+
always start with a correlator in which not polar field is complex conjugated.
|
|
2149
|
+
"""
|
|
2150
|
+
def __init__(self, min_sep, max_sep, **kwargs):
|
|
2151
|
+
|
|
2152
|
+
super().__init__(3, [0,2,2], n_cfs=2, min_sep=min_sep, max_sep=max_sep, **kwargs)
|
|
2153
|
+
self.nmax = self.nmaxs[0]
|
|
2154
|
+
self.phi = self.phis[0]
|
|
2155
|
+
self.projection = None
|
|
2156
|
+
self.projections_avail = [None, "X"]
|
|
2157
|
+
self.nbinsz_source = None
|
|
2158
|
+
self.nbinsz_lens = None
|
|
2159
|
+
|
|
2160
|
+
# (Add here any newly implemented projections)
|
|
2161
|
+
self._initprojections(self)
|
|
2162
|
+
|
|
2163
|
+
def __process_patches(self, cat_source, cat_lens, dotomo_source=True, dotomo_lens=True, rotsignflip=False,
|
|
2164
|
+
apply_edge_correction=False, save_patchres=False, save_filebase="", keep_patchres=False):
|
|
2165
|
+
if save_patchres:
|
|
2166
|
+
if not Path(save_patchres).is_dir():
|
|
2167
|
+
raise ValueError('Path to directory does not exist.')
|
|
2168
|
+
|
|
2169
|
+
for elp in range(cat_source.npatches):
|
|
2170
|
+
if self._verbose_python:
|
|
2171
|
+
print('Doing patch %i/%i'%(elp+1,cat_source.npatches))
|
|
2172
|
+
# Compute statistics on patch
|
|
2173
|
+
pscat = cat_source.frompatchind(elp,rotsignflip=rotsignflip)
|
|
2174
|
+
plcat = cat_lens.frompatchind(elp,rotsignflip=rotsignflip)
|
|
2175
|
+
pcorr = NGGCorrelation(
|
|
2176
|
+
min_sep=self.min_sep,
|
|
2177
|
+
max_sep=self.max_sep,
|
|
2178
|
+
nbinsr=self.nbinsr,
|
|
2179
|
+
nbinsphi=self.nbinsphi,
|
|
2180
|
+
nmaxs=self.nmaxs,
|
|
2181
|
+
method=self.method,
|
|
2182
|
+
multicountcorr=self.multicountcorr,
|
|
2183
|
+
shuffle_pix=self.shuffle_pix,
|
|
2184
|
+
tree_resos=self.tree_resos,
|
|
2185
|
+
rmin_pixsize=self.rmin_pixsize,
|
|
2186
|
+
resoshift_leafs=self.resoshift_leafs,
|
|
2187
|
+
minresoind_leaf=self.minresoind_leaf,
|
|
2188
|
+
maxresoind_leaf=self.maxresoind_leaf,
|
|
2189
|
+
nthreads=self.nthreads,
|
|
2190
|
+
verbosity=self.verbosity)
|
|
2191
|
+
pcorr.process(pscat, plcat, dotomo_source=dotomo_source, dotomo_lens=dotomo_lens)
|
|
2192
|
+
|
|
2193
|
+
# Update the total measurement
|
|
2194
|
+
if elp == 0:
|
|
2195
|
+
self.nbinsz_source = pcorr.nbinsz_source
|
|
2196
|
+
self.nbinsz_lens = pcorr.nbinsz_lens
|
|
2197
|
+
self.bin_centers = np.zeros_like(pcorr.bin_centers)
|
|
2198
|
+
self.npcf_multipoles = np.zeros_like(pcorr.npcf_multipoles)
|
|
2199
|
+
self.npcf_multipoles_norm = np.zeros_like(pcorr.npcf_multipoles_norm)
|
|
2200
|
+
_footnorm = np.zeros_like(pcorr.bin_centers)
|
|
2201
|
+
if keep_patchres:
|
|
2202
|
+
centers_patches = np.zeros((cat_source.npatches, *pcorr.bin_centers.shape), dtype=pcorr.bin_centers.dtype)
|
|
2203
|
+
npcf_multipoles_patches = np.zeros((cat_source.npatches, *pcorr.npcf_multipoles.shape), dtype=pcorr.npcf_multipoles.dtype)
|
|
2204
|
+
npcf_multipoles_norm_patches = np.zeros((cat_source.npatches, *pcorr.npcf_multipoles_norm.shape), dtype=pcorr.npcf_multipoles_norm.dtype)
|
|
2205
|
+
_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
|
|
2206
|
+
for i in range(pcorr.nbinsr)] for zs in range(self.nbinsz_source)] for zl in range(self.nbinsz_lens)])
|
|
2207
|
+
# Rough estimate of scaling of pair counts based on zeroth multipole of triplets. Note that we might get nans here due to numerical
|
|
2208
|
+
# inaccuracies in the multiple counting corrections for bins with zero triplets, so we force those values to be zero.
|
|
2209
|
+
_patchnorm = np.nan_to_num(np.sqrt(_shelltriplets))
|
|
2210
|
+
self.bin_centers += _patchnorm*pcorr.bin_centers
|
|
2211
|
+
_footnorm += _patchnorm
|
|
2212
|
+
self.npcf_multipoles += pcorr.npcf_multipoles
|
|
2213
|
+
self.npcf_multipoles_norm += pcorr.npcf_multipoles_norm
|
|
2214
|
+
if keep_patchres:
|
|
2215
|
+
centers_patches[elp] += pcorr.bin_centers
|
|
2216
|
+
npcf_multipoles_patches[elp] += pcorr.npcf_multipoles
|
|
2217
|
+
npcf_multipoles_norm_patches[elp] += pcorr.npcf_multipoles_norm
|
|
2218
|
+
if save_patchres:
|
|
2219
|
+
pcorr.saveinst(save_patchres, save_filebase+'_patch%i'%elp)
|
|
2220
|
+
|
|
2221
|
+
# Finalize the measurement on the full footprint
|
|
2222
|
+
self.bin_centers = np.divide(self.bin_centers,_footnorm, out=np.zeros_like(self.bin_centers), where=_footnorm>0)
|
|
2223
|
+
self.bin_centers_mean =np.mean(self.bin_centers, axis=(0,1))
|
|
2224
|
+
self.projection = "X"
|
|
2225
|
+
|
|
2226
|
+
if keep_patchres:
|
|
2227
|
+
return centers_patches, npcf_multipoles_patches, npcf_multipoles_norm_patches
|
|
2228
|
+
|
|
2229
|
+
def process(self, cat_source, cat_lens, dotomo_source=True, dotomo_lens=True, rotsignflip=False, apply_edge_correction=False,
|
|
2230
|
+
save_patchres=False, save_filebase="", keep_patchres=False):
|
|
2231
|
+
r"""
|
|
2232
|
+
Compute a lens-shear-shear correlation provided a source and a lens catalog.
|
|
2233
|
+
|
|
2234
|
+
Parameters
|
|
2235
|
+
----------
|
|
2236
|
+
cat_source: orpheus.SpinTracerCatalog
|
|
2237
|
+
The source catalog which is processed
|
|
2238
|
+
cat_lens: orpheus.ScalarTracerCatalog
|
|
2239
|
+
The lens catalog which is processed
|
|
2240
|
+
dotomo_source: bool
|
|
2241
|
+
Flag that decides whether the tomographic information in the source catalog should be used. Defaults to `True`.
|
|
2242
|
+
dotomo_lens: bool
|
|
2243
|
+
Flag that decides whether the tomographic information in the lens catalog should be used. Defaults to `True`.
|
|
2244
|
+
rotsignflip: bool
|
|
2245
|
+
If the shape catalog is has been decomposed in patches, choose whether the rotation angle should be flipped.
|
|
2246
|
+
For simulated data this was always ok to set to 'False`. Defaults to `False`.
|
|
2247
|
+
apply_edge_correction: bool
|
|
2248
|
+
Flag that decides how the NPCF in the real space basis is computed.
|
|
2249
|
+
* If set to `True` the computation is done via edge-correcting the GNN-multipoles
|
|
2250
|
+
* If set to `False` both GNN and NNN are transformed separately and the ratio is done in the real-space basis
|
|
2251
|
+
Defaults to `False`.
|
|
2252
|
+
save_patchres: bool or str
|
|
2253
|
+
If the shape catalog is has been decomposed in patches, flag whether to save the GG measurements on the individual patches.
|
|
2254
|
+
Note that the path needs to exist, otherwise a `ValueError` is raised. For a flat-sky catalog this parameter
|
|
2255
|
+
has no effect. Defaults to `False`
|
|
2256
|
+
save_filebase: str
|
|
2257
|
+
Base of the filenames in which the patches are saved. The full filename will be `<save_patchres>/<save_filebase>_patchxx.npz`.
|
|
2258
|
+
Only has an effect if the shape catalog consists of multiple patches and `save_patchres` is not `False`.
|
|
2259
|
+
keep_patchres: bool
|
|
2260
|
+
If the catalog consists of multiple patches, returns all measurements on the patches. Defaults to `False`.
|
|
2261
|
+
"""
|
|
2262
|
+
|
|
2263
|
+
self._checkcats([cat_lens, cat_source, cat_source], [0, 2, 2])
|
|
2264
|
+
|
|
2265
|
+
# Catch typical errors, i.e. incompatible catalogs or missin patch decompositions
|
|
2266
|
+
if cat_source.geometry=='spherical' and cat_source.patchinds is None:
|
|
2267
|
+
raise ValueError('Error: Spherical catalog needs to be first decomposed into patches using the Catalog._topatches method.')
|
|
2268
|
+
if cat_lens.geometry=='spherical' and cat_lens.patchinds is None:
|
|
2269
|
+
raise ValueError('Error: Spherical catalog needs to be first decomposed into patches using the Catalog._topatches method.')
|
|
2270
|
+
if cat_source.geometry != cat_lens.geometry:
|
|
2271
|
+
raise ValueError('Incompatible geometries of source catalog (%s) and lens catalog (%s).'%(
|
|
2272
|
+
cat_source.geometry,cat_lens.geometry))
|
|
2273
|
+
|
|
2274
|
+
# Catalog consist of multiple patches
|
|
2275
|
+
if (cat_source.patchinds is not None) and (cat_lens.patchinds is not None):
|
|
2276
|
+
return self.__process_patches(cat_source, cat_lens, dotomo_source=dotomo_source, dotomo_lens=dotomo_lens,
|
|
2277
|
+
rotsignflip=rotsignflip, apply_edge_correction=apply_edge_correction,
|
|
2278
|
+
save_patchres=save_patchres, save_filebase=save_filebase, keep_patchres=keep_patchres)
|
|
2279
|
+
|
|
2280
|
+
# Catalog does not consist of patches
|
|
2281
|
+
else:
|
|
2282
|
+
if not dotomo_source:
|
|
2283
|
+
self.nbinsz_source = 1
|
|
2284
|
+
old_zbins_source = cat_source.zbins[:]
|
|
2285
|
+
cat_source.zbins = np.zeros(cat_source.ngal, dtype=np.int32)
|
|
2286
|
+
else:
|
|
2287
|
+
self.nbinsz_source = cat_source.nbinsz
|
|
2288
|
+
if not dotomo_lens:
|
|
2289
|
+
self.nbinsz_lens = 1
|
|
2290
|
+
old_zbins_lens = cat_lens.zbins[:]
|
|
2291
|
+
cat_lens.zbins = np.zeros(cat_lens.ngal, dtype=np.int32)
|
|
2292
|
+
else:
|
|
2293
|
+
self.nbinsz_lens = cat_lens.nbinsz
|
|
2294
|
+
|
|
2295
|
+
_z3combis = self.nbinsz_lens*self.nbinsz_source*self.nbinsz_source
|
|
2296
|
+
_r2combis = self.nbinsr*self.nbinsr
|
|
2297
|
+
sc = (self.n_cfs, 2*self.nmax+1, _z3combis, self.nbinsr, self.nbinsr)
|
|
2298
|
+
sn = (2*self.nmax+1, _z3combis, self.nbinsr,self.nbinsr)
|
|
2299
|
+
szr = (self.nbinsz_lens, self.nbinsz_source, self.nbinsr)
|
|
2300
|
+
bin_centers = np.zeros(reduce(operator.mul, szr)).astype(np.float64)
|
|
2301
|
+
Upsilon_n = np.zeros(reduce(operator.mul, sc)).astype(np.complex128)
|
|
2302
|
+
Norm_n = np.zeros(reduce(operator.mul, sn)).astype(np.complex128)
|
|
2303
|
+
args_sourcecat = (cat_source.weight.astype(np.float64),
|
|
2304
|
+
cat_source.pos1.astype(np.float64), cat_source.pos2.astype(np.float64),
|
|
2305
|
+
cat_source.tracer_1.astype(np.float64), cat_source.tracer_2.astype(np.float64),
|
|
2306
|
+
cat_source.zbins.astype(np.int32), np.int32(self.nbinsz_source), np.int32(cat_source.ngal), )
|
|
2307
|
+
args_lenscat = (cat_lens.isinner.astype(np.float64), cat_lens.weight.astype(np.float64), cat_lens.pos1.astype(np.float64),
|
|
2308
|
+
cat_lens.pos2.astype(np.float64), cat_lens.zbins.astype(np.int32),
|
|
2309
|
+
np.int32(self.nbinsz_lens), np.int32(cat_lens.ngal), )
|
|
2310
|
+
args_basesetup = (np.int32(self.nmax), np.float64(self.min_sep), np.float64(self.max_sep),
|
|
2311
|
+
np.int32(self.nbinsr), np.int32(self.multicountcorr), )
|
|
2312
|
+
if self.method=="Discrete":
|
|
2313
|
+
hash_dpix = max(1.,self.max_sep//10.)
|
|
2314
|
+
jointextent = list(cat_source._jointextent([cat_lens], extend=self.tree_resos[-1]))
|
|
2315
|
+
cat_source.build_spatialhash(dpix=hash_dpix, extent=jointextent)
|
|
2316
|
+
cat_lens.build_spatialhash(dpix=hash_dpix, extent=jointextent)
|
|
2317
|
+
nregions = np.int32(len(np.argwhere(cat_lens.index_matcher>-1).flatten()))
|
|
2318
|
+
args_hash = (cat_source.index_matcher, cat_source.pixs_galind_bounds, cat_source.pix_gals,
|
|
2319
|
+
cat_lens.index_matcher, cat_lens.pixs_galind_bounds, cat_lens.pix_gals, nregions, )
|
|
2320
|
+
args_pixgrid = (np.float64(cat_lens.pix1_start), np.float64(cat_lens.pix1_d), np.int32(cat_lens.pix1_n),
|
|
2321
|
+
np.float64(cat_lens.pix2_start), np.float64(cat_lens.pix2_d), np.int32(cat_lens.pix2_n), )
|
|
2322
|
+
args = (*args_sourcecat,
|
|
2323
|
+
*args_lenscat,
|
|
2324
|
+
*args_basesetup,
|
|
2325
|
+
*args_hash,
|
|
2326
|
+
*args_pixgrid,
|
|
2327
|
+
np.int32(self.nthreads),
|
|
2328
|
+
np.int32(self._verbose_c),
|
|
2329
|
+
bin_centers,
|
|
2330
|
+
Upsilon_n,
|
|
2331
|
+
Norm_n, )
|
|
2332
|
+
func = self.clib.alloc_Gammans_discrete_NGG
|
|
2333
|
+
if self.method=="Tree" or self.method == "DoubleTree":
|
|
2334
|
+
cutfirst = np.int32(self.tree_resos[0]==0.)
|
|
2335
|
+
jointextent = list(cat_source._jointextent([cat_lens], extend=self.tree_resos[-1]))
|
|
2336
|
+
# Build multihashes for sources and lenses
|
|
2337
|
+
mhash_source = cat_source.multihash(dpixs=self.tree_resos[cutfirst:], dpix_hash=self.tree_resos[-1],
|
|
2338
|
+
shuffle=self.shuffle_pix, normed=True, extent=jointextent)
|
|
2339
|
+
sngal_resos, spos1s, spos2s, sweights, szbins, sisinners, sallfields, sindex_matchers, \
|
|
2340
|
+
spixs_galind_bounds, spix_gals, sdpixs1_true, sdpixs2_true = mhash_source
|
|
2341
|
+
ngal_resos_source = np.array(sngal_resos, dtype=np.int32)
|
|
2342
|
+
weight_resos_source = np.concatenate(sweights).astype(np.float64)
|
|
2343
|
+
pos1_resos_source = np.concatenate(spos1s).astype(np.float64)
|
|
2344
|
+
pos2_resos_source = np.concatenate(spos2s).astype(np.float64)
|
|
2345
|
+
zbin_resos_source = np.concatenate(szbins).astype(np.int32)
|
|
2346
|
+
isinner_resos_source = np.concatenate(sisinners).astype(np.float64)
|
|
2347
|
+
e1_resos_source = np.concatenate([sallfields[i][0] for i in range(len(sallfields))]).astype(np.float64)
|
|
2348
|
+
e2_resos_source = np.concatenate([sallfields[i][1] for i in range(len(sallfields))]).astype(np.float64)
|
|
2349
|
+
index_matcher_source = np.concatenate(sindex_matchers).astype(np.int32)
|
|
2350
|
+
pixs_galind_bounds_source = np.concatenate(spixs_galind_bounds).astype(np.int32)
|
|
2351
|
+
pix_gals_source = np.concatenate(spix_gals).astype(np.int32)
|
|
2352
|
+
mhash_lens = cat_lens.multihash(dpixs=self.tree_resos[cutfirst:], dpix_hash=self.tree_resos[-1],
|
|
2353
|
+
shuffle=self.shuffle_pix, normed=True, extent=jointextent)
|
|
2354
|
+
lngal_resos, lpos1s, lpos2s, lweights, lzbins, lisinners, lallfields, lindex_matchers, \
|
|
2355
|
+
lpixs_galind_bounds, lpix_gals, ldpixs1_true, ldpixs2_true = mhash_lens
|
|
2356
|
+
ngal_resos_lens = np.array(lngal_resos, dtype=np.int32)
|
|
2357
|
+
weight_resos_lens = np.concatenate(lweights).astype(np.float64)
|
|
2358
|
+
pos1_resos_lens = np.concatenate(lpos1s).astype(np.float64)
|
|
2359
|
+
pos2_resos_lens = np.concatenate(lpos2s).astype(np.float64)
|
|
2360
|
+
zbin_resos_lens = np.concatenate(lzbins).astype(np.int32)
|
|
2361
|
+
isinner_resos_lens = np.concatenate(lisinners).astype(np.float64)
|
|
2362
|
+
index_matcher_lens = np.concatenate(lindex_matchers).astype(np.int32)
|
|
2363
|
+
pixs_galind_bounds_lens = np.concatenate(lpixs_galind_bounds).astype(np.int32)
|
|
2364
|
+
pix_gals_lens = np.asarray(np.concatenate(lpix_gals)).astype(np.int32)
|
|
2365
|
+
index_matcher_flat = np.argwhere(cat_lens.index_matcher>-1).flatten().astype(np.int32)
|
|
2366
|
+
nregions = np.int32(len(index_matcher_flat))
|
|
2367
|
+
if self.method=="Tree":
|
|
2368
|
+
# Collect args
|
|
2369
|
+
args_resoinfo = (np.int32(self.tree_nresos), self.tree_redges,)
|
|
2370
|
+
args_resos_sourcecat = (weight_resos_source, pos1_resos_source, pos2_resos_source,
|
|
2371
|
+
e1_resos_source, e2_resos_source, zbin_resos_source,
|
|
2372
|
+
np.int32(self.nbinsz_source), ngal_resos_source, )
|
|
2373
|
+
args_mhash = (index_matcher_source, pixs_galind_bounds_source, pix_gals_source,
|
|
2374
|
+
index_matcher_lens, pixs_galind_bounds_lens, pix_gals_lens, nregions, )
|
|
2375
|
+
args_pixgrid = (np.float64(cat_lens.pix1_start), np.float64(cat_lens.pix1_d), np.int32(cat_lens.pix1_n),
|
|
2376
|
+
np.float64(cat_lens.pix2_start), np.float64(cat_lens.pix2_d), np.int32(cat_lens.pix2_n), )
|
|
2377
|
+
args = (*args_resoinfo,
|
|
2378
|
+
*args_resos_sourcecat,
|
|
2379
|
+
*args_lenscat,
|
|
2380
|
+
*args_mhash,
|
|
2381
|
+
*args_pixgrid,
|
|
2382
|
+
*args_basesetup,
|
|
2383
|
+
np.int32(self.nthreads),
|
|
2384
|
+
np.int32(self._verbose_c),
|
|
2385
|
+
bin_centers,
|
|
2386
|
+
Upsilon_n,
|
|
2387
|
+
Norm_n, )
|
|
2388
|
+
func = self.clib.alloc_Gammans_tree_NGG
|
|
2389
|
+
if self.method == "DoubleTree":
|
|
2390
|
+
# Collect args
|
|
2391
|
+
args_resoinfo = (np.int32(self.tree_nresos), np.int32(self.tree_nresos-cutfirst),
|
|
2392
|
+
sdpixs1_true.astype(np.float64), sdpixs2_true.astype(np.float64), self.tree_redges, )
|
|
2393
|
+
args_leafs = (np.int32(self.resoshift_leafs), np.int32(self.minresoind_leaf),
|
|
2394
|
+
np.int32(self.maxresoind_leaf), )
|
|
2395
|
+
args_resos = (isinner_resos_source, weight_resos_source, pos1_resos_source, pos2_resos_source,
|
|
2396
|
+
e1_resos_source, e2_resos_source, zbin_resos_source, ngal_resos_source,
|
|
2397
|
+
np.int32(self.nbinsz_source), isinner_resos_lens, weight_resos_lens, pos1_resos_lens,
|
|
2398
|
+
pos2_resos_lens, zbin_resos_lens, ngal_resos_lens, np.int32(self.nbinsz_lens), )
|
|
2399
|
+
args_mhash = (index_matcher_source, pixs_galind_bounds_source, pix_gals_source,
|
|
2400
|
+
index_matcher_lens, pixs_galind_bounds_lens, pix_gals_lens, index_matcher_flat, nregions, )
|
|
2401
|
+
args_pixgrid = (np.float64(cat_lens.pix1_start), np.float64(cat_lens.pix1_d), np.int32(cat_lens.pix1_n),
|
|
2402
|
+
np.float64(cat_lens.pix2_start), np.float64(cat_lens.pix2_d), np.int32(cat_lens.pix2_n), )
|
|
2403
|
+
args = (*args_resoinfo,
|
|
2404
|
+
*args_leafs,
|
|
2405
|
+
*args_resos,
|
|
2406
|
+
*args_basesetup,
|
|
2407
|
+
*args_mhash,
|
|
2408
|
+
*args_pixgrid,
|
|
2409
|
+
np.int32(self.nthreads),
|
|
2410
|
+
np.int32(self._verbose_c),
|
|
2411
|
+
bin_centers,
|
|
2412
|
+
Upsilon_n,
|
|
2413
|
+
Norm_n, )
|
|
2414
|
+
func = self.clib.alloc_Gammans_doubletree_NGG
|
|
2415
|
+
if self._verbose_debug:
|
|
2416
|
+
for elarg, arg in enumerate(args):
|
|
2417
|
+
toprint = (elarg, type(arg),)
|
|
2418
|
+
if isinstance(arg, np.ndarray):
|
|
2419
|
+
toprint += (type(arg[0]), arg.shape)
|
|
2420
|
+
try:
|
|
2421
|
+
toprint += (func.argtypes[elarg], )
|
|
2422
|
+
print(toprint)
|
|
2423
|
+
print(arg)
|
|
2424
|
+
except:
|
|
2425
|
+
print("We did have a problem for arg %i"%elarg)
|
|
2426
|
+
|
|
2427
|
+
func(*args)
|
|
2428
|
+
|
|
2429
|
+
# Components of npcf are ordered as (Ups_-, Ups_+)
|
|
2430
|
+
self.bin_centers = bin_centers.reshape(szr)
|
|
2431
|
+
self.bin_centers_mean = np.mean(self.bin_centers, axis=(0,1))
|
|
2432
|
+
self.npcf_multipoles = Upsilon_n.reshape(sc)
|
|
2433
|
+
self.npcf_multipoles_norm = Norm_n.reshape(sn)
|
|
2434
|
+
self.projection = "X"
|
|
2435
|
+
self.is_edge_corrected = False
|
|
2436
|
+
|
|
2437
|
+
if apply_edge_correction:
|
|
2438
|
+
self.edge_correction()
|
|
2439
|
+
|
|
2440
|
+
if not dotomo_source:
|
|
2441
|
+
cat_source.zbins = old_zbins_source
|
|
2442
|
+
if not dotomo_lens:
|
|
2443
|
+
cat_lens.zbins = old_zbins_lens
|
|
2444
|
+
|
|
2445
|
+
def edge_correction(self, ret_matrices=False):
|
|
2446
|
+
|
|
2447
|
+
assert(not self.is_edge_corrected)
|
|
2448
|
+
def gen_M_matrix(thet1,thet2,threepcf_n_norm):
|
|
2449
|
+
nvals, ntheta, _ = threepcf_n_norm.shape
|
|
2450
|
+
nmax = (nvals-1)//2
|
|
2451
|
+
narr = np.arange(-nmax,nmax+1, dtype=np.int)
|
|
2452
|
+
nextM = np.zeros((nvals,nvals))
|
|
2453
|
+
for ind, ell in enumerate(narr):
|
|
2454
|
+
lminusn = ell-narr
|
|
2455
|
+
sel = np.logical_and(lminusn+nmax>=0, lminusn+nmax<nvals)
|
|
2456
|
+
nextM[ind,sel] = threepcf_n_norm[(lminusn+nmax)[sel],thet1,thet2].real / threepcf_n_norm[nmax,thet1,thet2].real
|
|
2457
|
+
return nextM
|
|
2458
|
+
|
|
2459
|
+
_nvals, nzcombis, ntheta, _ = self.npcf_multipoles_norm.shape
|
|
2460
|
+
nvals = int((_nvals-1)/2)
|
|
2461
|
+
nmax = nvals-1
|
|
2462
|
+
threepcf_n_corr = np.zeros_like(self.npcf_multipoles)
|
|
2463
|
+
if ret_matrices:
|
|
2464
|
+
mats = np.zeros((nzcombis,ntheta,ntheta,nvals,nvals))
|
|
2465
|
+
for indz in range(nzcombis):
|
|
2466
|
+
#sys.stdout.write("%i"%indz)
|
|
2467
|
+
for thet1 in range(ntheta):
|
|
2468
|
+
for thet2 in range(ntheta):
|
|
2469
|
+
nextM = gen_M_matrix(thet1,thet2,self.npcf_multipoles_norm[:,indz])
|
|
2470
|
+
nextM_inv = np.linalg.inv(nextM)
|
|
2471
|
+
if ret_matrices:
|
|
2472
|
+
mats[indz,thet1,thet2] = nextM
|
|
2473
|
+
for el_cf in range(self.n_cfs):
|
|
2474
|
+
threepcf_n_corr[el_cf,:,indz,thet1,thet2] = np.matmul(
|
|
2475
|
+
nextM_inv,self.npcf_multipoles[el_cf,:,indz,thet1,thet2])
|
|
2476
|
+
|
|
2477
|
+
self.npcf_multipoles = threepcf_n_corr
|
|
2478
|
+
self.is_edge_corrected = True
|
|
2479
|
+
|
|
2480
|
+
if ret_matrices:
|
|
2481
|
+
return threepcf_n_corr, mats
|
|
2482
|
+
|
|
2483
|
+
def multipoles2npcf(self, integrated=False):
|
|
2484
|
+
r"""
|
|
2485
|
+
Notes
|
|
2486
|
+
-----
|
|
2487
|
+
* When dividing by the (weighted) counts ``N``, all contributions for which ``N <= 0`` are set to zero.
|
|
2488
|
+
|
|
2489
|
+
"""
|
|
2490
|
+
_, nzcombis, rbins, rbins = np.shape(self.npcf_multipoles[0])
|
|
2491
|
+
self.npcf = np.zeros((self.n_cfs, nzcombis, rbins, rbins, len(self.phi)), dtype=complex)
|
|
2492
|
+
self.npcf_norm = np.zeros((nzcombis, rbins, rbins, len(self.phi)), dtype=float)
|
|
2493
|
+
ztiler = np.arange(self.nbinsz_lens*self.nbinsz_source*self.nbinsz_source).reshape(
|
|
2494
|
+
(self.nbinsz_lens,self.nbinsz_source,self.nbinsz_source)).transpose(0,2,1).flatten().astype(np.int32)
|
|
2495
|
+
|
|
2496
|
+
# NGG components
|
|
2497
|
+
for elphi, phi in enumerate(self.phi):
|
|
2498
|
+
tmp = np.zeros((self.n_cfs, nzcombis, rbins, rbins),dtype=complex)
|
|
2499
|
+
tmpnorm = np.zeros((nzcombis, rbins, rbins),dtype=complex)
|
|
2500
|
+
for n in range(2*self.nmax+1):
|
|
2501
|
+
dphi = self.phi[1] - self.phi[0]
|
|
2502
|
+
if integrated:
|
|
2503
|
+
if n==self.nmax:
|
|
2504
|
+
ifac = dphi
|
|
2505
|
+
else:
|
|
2506
|
+
ifac = 2./(n-self.nmax) * np.sin((n-self.nmax)*dphi/2.)
|
|
2507
|
+
else:
|
|
2508
|
+
ifac = dphi
|
|
2509
|
+
_const = 1./(2*np.pi) * np.exp(1J*(n-self.nmax)*phi) * ifac
|
|
2510
|
+
tmpnorm += _const * self.npcf_multipoles_norm[n].astype(complex)
|
|
2511
|
+
for el_cf in range(self.n_cfs):
|
|
2512
|
+
tmp[el_cf] += _const * self.npcf_multipoles[el_cf,n].astype(complex)
|
|
2513
|
+
self.npcf[...,elphi] = tmp
|
|
2514
|
+
self.npcf_norm[...,elphi] = tmpnorm.real
|
|
2515
|
+
|
|
2516
|
+
if self.is_edge_corrected:
|
|
2517
|
+
N0 = dphi/(2*np.pi) * self.npcf_multipoles_norm[self.nmax].astype(complex)
|
|
2518
|
+
sel_zero = np.isnan(N0)
|
|
2519
|
+
_a = self.npcf
|
|
2520
|
+
_b = N0.real[np.newaxis, :, :, :, np.newaxis]
|
|
2521
|
+
self.npcf = np.divide(_a, _b, out=np.zeros_like(_a), where=_b>0)
|
|
2522
|
+
else:
|
|
2523
|
+
_a = self.npcf
|
|
2524
|
+
_b = self.npcf_norm
|
|
2525
|
+
self.npcf = np.divide(_a, _b, out=np.zeros_like(_a), where=_b>0)
|
|
2526
|
+
#self.npcf = self.npcf/self.npcf_norm[0][None, ...].astype(complex)
|
|
2527
|
+
self.projection = "X"
|
|
2528
|
+
|
|
2529
|
+
## PROJECTIONS ##
|
|
2530
|
+
def projectnpcf(self, projection):
|
|
2531
|
+
super()._projectnpcf(self, projection)
|
|
2532
|
+
|
|
2533
|
+
## INTEGRATED MEASURES ##
|
|
2534
|
+
def computeNMM(self, radii, do_multiscale=False, tofile=False, filtercache=None):
|
|
2535
|
+
"""
|
|
2536
|
+
Compute third-order aperture statistics
|
|
2537
|
+
"""
|
|
2538
|
+
|
|
2539
|
+
nb_config.NUMBA_DEFAULT_NUM_THREADS = self.nthreads
|
|
2540
|
+
nb_config.NUMBA_NUM_THREADS = self.nthreads
|
|
2541
|
+
|
|
2542
|
+
if self.npcf is None and self.npcf_multipoles is not None:
|
|
2543
|
+
self.multipoles2npcf()
|
|
2544
|
+
|
|
2545
|
+
nradii = len(radii)
|
|
2546
|
+
if not do_multiscale:
|
|
2547
|
+
nrcombis = nradii
|
|
2548
|
+
_rcut = 1
|
|
2549
|
+
else:
|
|
2550
|
+
nrcombis = nradii*nradii*nradii
|
|
2551
|
+
_rcut = nradii
|
|
2552
|
+
NMM = np.zeros((3, self.nbinsz_lens*self.nbinsz_source*self.nbinsz_source, nrcombis), dtype=complex)
|
|
2553
|
+
tmprcombi = 0
|
|
2554
|
+
for elr1, R1 in enumerate(radii):
|
|
2555
|
+
for elr2, R2 in enumerate(radii[:_rcut]):
|
|
2556
|
+
for elr3, R3 in enumerate(radii[:_rcut]):
|
|
2557
|
+
if not do_multiscale:
|
|
2558
|
+
R2 = R1
|
|
2559
|
+
R3 = R1
|
|
2560
|
+
if filtercache is not None:
|
|
2561
|
+
A_NMM = filtercache[tmprcombi]
|
|
2562
|
+
else:
|
|
2563
|
+
A_NMM = self._NMM_filtergrid(R1, R2, R3)
|
|
2564
|
+
_NMM = np.nansum(A_NMM[0]*self.npcf[0,...],axis=(1,2,3))
|
|
2565
|
+
_NMMstar = np.nansum(A_NMM[1]*self.npcf[1,...],axis=(1,2,3))
|
|
2566
|
+
NMM[0,:,tmprcombi] = (_NMM + _NMMstar).real/2.
|
|
2567
|
+
NMM[1,:,tmprcombi] = (-_NMM + _NMMstar).real/2.
|
|
2568
|
+
NMM[2,:,tmprcombi] = (_NMM + _NMMstar).imag/2.
|
|
2569
|
+
tmprcombi += 1
|
|
2570
|
+
return NMM
|
|
2571
|
+
|
|
2572
|
+
def _NMM_filtergrid(self, R1, R2, R3):
|
|
2573
|
+
return self.__NMM_filtergrid(R1, R2, R3, self.bin_edges, self.bin_centers_mean, self.phi)
|
|
2574
|
+
|
|
2575
|
+
@staticmethod
|
|
2576
|
+
@jit(nopython=True, parallel=True)
|
|
2577
|
+
def __NMM_filtergrid(R1, R2, R3, edges, centers, phis):
|
|
2578
|
+
nbinsr = len(centers)
|
|
2579
|
+
nbinsphi = len(phis)
|
|
2580
|
+
_cphis = np.cos(phis)
|
|
2581
|
+
_ephis = np.e**(1J*phis)
|
|
2582
|
+
_ephisc = np.e**(-1J*phis)
|
|
2583
|
+
Theta4 = 1./3. * (R1**2*R2**2 + R1**2*R3**2 + R2**2*R3**2)
|
|
2584
|
+
a2 = 2./3. * R1**2*R2**2*R3**2 / Theta4
|
|
2585
|
+
ANMM = np.zeros((2,nbinsr,nbinsr,nbinsphi), dtype=nb_complex128)
|
|
2586
|
+
for elb in prange(nbinsr*nbinsr):
|
|
2587
|
+
elb1 = int(elb//nbinsr)
|
|
2588
|
+
elb2 = elb%nbinsr
|
|
2589
|
+
_y1 = centers[elb1]
|
|
2590
|
+
_dbin1 = edges[elb1+1] - edges[elb1]
|
|
2591
|
+
_y2 = centers[elb2]
|
|
2592
|
+
_dbin2 = edges[elb2+1] - edges[elb2]
|
|
2593
|
+
_dbinphi = phis[1] - phis[0]
|
|
2594
|
+
|
|
2595
|
+
csq = a2**2/4. * (_y1**2/R1**4 + _y2**2/R2**4 + 2*_y1*_y2*_cphis/(R1**2*R2**2))
|
|
2596
|
+
b0 = _y1**2/(2*R1**2)+_y2**2/(2*R2**2) - csq/a2
|
|
2597
|
+
|
|
2598
|
+
g1 = _y1 - a2/2. * (_y1/R1**2 + _y2*_ephisc/R2**2)
|
|
2599
|
+
g2 = _y2 - a2/2. * (_y2/R2**2 + _y1*_ephis/R1**2)
|
|
2600
|
+
g1c = g1.conj()
|
|
2601
|
+
g2c = g2.conj()
|
|
2602
|
+
pref = np.e**(-b0)/(72*np.pi*Theta4**2)
|
|
2603
|
+
_h1 = 2*(g2c*_y1+g1*_y2-2*g1*g2c)*(g1*g2c+2*a2*_ephisc)
|
|
2604
|
+
_h2 = 2*a2*(2*R3**2-csq-3*a2)*_ephisc*_ephisc
|
|
2605
|
+
_h3 = 4*g1*g2c*(2*R3**2-csq-2*a2)*_ephisc
|
|
2606
|
+
_h4 = (g1*g2c)**2/a2 * (2*R3**2-csq-a2)
|
|
2607
|
+
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))
|
|
2608
|
+
sum_MMstarN = pref * (_h1 + _h2 + _h3 + _h4)
|
|
2609
|
+
_measures = _y1*_dbin1 * _y2*_dbin2 * _dbinphi
|
|
2610
|
+
|
|
2611
|
+
ANMM[0,elb1,elb2] = _measures * sum_MMN
|
|
2612
|
+
ANMM[1,elb1,elb2] = _measures * sum_MMstarN
|
|
2613
|
+
|
|
2614
|
+
return ANMM
|
|
2615
|
+
|
|
2616
|
+
#############################
|
|
2617
|
+
## FOURTH-ORDER STATISTICS ##
|
|
2618
|
+
#############################
|
|
2619
|
+
class GGGGCorrelation_NoTomo(BinnedNPCF):
|
|
2620
|
+
r""" Class containing methods to measure and and obtain statistics that are built
|
|
2621
|
+
from nontomographic fourth-order shear correlation functions.
|
|
2622
|
+
|
|
2623
|
+
Attributes
|
|
2624
|
+
----------
|
|
2625
|
+
min_sep: float
|
|
2626
|
+
The smallest distance of each vertex for which the NPCF is computed.
|
|
2627
|
+
max_sep: float
|
|
2628
|
+
The largest distance of each vertex for which the NPCF is computed.
|
|
2629
|
+
thetabatchsize_max: int, optional
|
|
2630
|
+
The largest number of radial bin combinations that are processed in parallel.
|
|
2631
|
+
Defaults to ``10 000``.
|
|
2632
|
+
|
|
2633
|
+
Notes
|
|
2634
|
+
-----
|
|
2635
|
+
Inherits all other parameters and attributes from :class:`BinnedNPCF`.
|
|
2636
|
+
Additional child-specific parameters can be passed via ``kwargs``.
|
|
2637
|
+
Either ``nbinsr`` or ``binsize`` has to be provided to fix the binning scheme .
|
|
2638
|
+
|
|
2639
|
+
"""
|
|
2640
|
+
|
|
2641
|
+
def __init__(self, min_sep, max_sep, thetabatchsize_max=10000, method="Tree", **kwargs):
|
|
2642
|
+
|
|
2643
|
+
super().__init__(order=4, spins=np.array([2,2,2,2], dtype=np.int32),
|
|
2644
|
+
n_cfs=8, min_sep=min_sep, max_sep=max_sep,
|
|
2645
|
+
method=method, methods_avail=["Discrete", "Tree"], **kwargs)
|
|
2646
|
+
|
|
2647
|
+
self.thetabatchsize_max = thetabatchsize_max
|
|
2648
|
+
self.projection = None
|
|
2649
|
+
self.projections_avail = [None, "X", "Centroid"]
|
|
2650
|
+
self.proj_dict = {"X":0, "Centroid":1}
|
|
2651
|
+
self.nbinsz = 1
|
|
2652
|
+
self.nzcombis = 1
|
|
2653
|
+
|
|
2654
|
+
# (Add here any newly implemented projections)
|
|
2655
|
+
self._initprojections(self)
|
|
2656
|
+
self.project["X"]["Centroid"] = self._x2centroid
|
|
2657
|
+
|
|
2658
|
+
def process(self, cat, statistics="all", tofile=False, apply_edge_correction=False, projection="X",
|
|
2659
|
+
lowmem=None, mapradii=None, batchsize=None, custom_thetacombis=None, cutlen=2**31-1):
|
|
2660
|
+
r"""
|
|
2661
|
+
Arguments:
|
|
2662
|
+
|
|
2663
|
+
Logic works as follows:
|
|
2664
|
+
* Keyword 'statistics' \in [4pcf_real, 4pcf_multipoles, M4, Map4, M4c, Map4c, allMap, all4pcf, all]
|
|
2665
|
+
* - If 4pcf_multipoles in statistics --> save 4pcf_multipoles
|
|
2666
|
+
* - If 4pcf_real in statistics --> save 4pcf_real
|
|
2667
|
+
* - If only M4 in statistics --> Do not save any 4pcf. This is really the lowmem case.
|
|
2668
|
+
* - allMap, all4pcf, all are abbreviations as expected
|
|
2669
|
+
* If lowmem=True, uses the inefficient, but lowmem function for computation and output statistics
|
|
2670
|
+
from there as wanted.
|
|
2671
|
+
* If lowmem=False, use the fast functions to do the 4pcf multipole computation and do
|
|
2672
|
+
the potential conversions lateron.
|
|
2673
|
+
* Default lowmem to None and
|
|
2674
|
+
* - Set to true if any aperture statistics is in stats or we will run into mem error
|
|
2675
|
+
* - Set to false otherwise
|
|
2676
|
+
* - Raise error if lowmen=False and we will have more that 2^31-1 elements at any stage of the computation
|
|
2677
|
+
|
|
2678
|
+
custom_thetacombis: array of inds which theta combis will be selected
|
|
2679
|
+
"""
|
|
2680
|
+
|
|
2681
|
+
## Preparations ##
|
|
2682
|
+
# Build list of statistics to be calculated
|
|
2683
|
+
statistics_avail_4pcf = ["4pcf_real", "4pcf_multipole"]
|
|
2684
|
+
statistics_avail_map4 = ["M4", "Map4", "M4c", "Map4c"]
|
|
2685
|
+
statistics_avail_comp = ["allMap", "all4pcf", "all"]
|
|
2686
|
+
statistics_avail_phys = statistics_avail_4pcf + statistics_avail_map4
|
|
2687
|
+
statistics_avail = statistics_avail_4pcf + statistics_avail_map4 + statistics_avail_comp
|
|
2688
|
+
_statistics = []
|
|
2689
|
+
hasintegratedstats = False
|
|
2690
|
+
_strbadstats = lambda stat: ("The statistics `%s` has not been implemented yet. "%stat +
|
|
2691
|
+
"Currently supported statistics are:\n" + str(statistics_avail))
|
|
2692
|
+
if type(statistics) not in [list, str]:
|
|
2693
|
+
raise ValueError("The parameter `statistics` should either be a list or a string.")
|
|
2694
|
+
if type(statistics) is str:
|
|
2695
|
+
if statistics not in statistics_avail:
|
|
2696
|
+
raise ValueError(_strbadstats)
|
|
2697
|
+
statistics = [statistics]
|
|
2698
|
+
if type(statistics) is list:
|
|
2699
|
+
if "all" in statistics:
|
|
2700
|
+
_statistics = statistics_avail_phys
|
|
2701
|
+
elif "all4pcf" in statistics:
|
|
2702
|
+
_statistics.append(statistics_avail_4pcf)
|
|
2703
|
+
elif "allMap" in statistics:
|
|
2704
|
+
_statistics.append(statistics_avail_map4)
|
|
2705
|
+
_statistics = flatlist(_statistics)
|
|
2706
|
+
for stat in statistics:
|
|
2707
|
+
if stat not in statistics_avail:
|
|
2708
|
+
raise ValueError(_strbadstats)
|
|
2709
|
+
if stat in statistics_avail_phys and stat not in _statistics:
|
|
2710
|
+
_statistics.append(stat)
|
|
2711
|
+
statistics = list(set(flatlist(_statistics)))
|
|
2712
|
+
for stat in statistics:
|
|
2713
|
+
if stat in statistics_avail_map4:
|
|
2714
|
+
hasintegratedstats = True
|
|
2715
|
+
|
|
2716
|
+
# Check if the output will fit in memory
|
|
2717
|
+
if "4pcf_multipole" in statistics:
|
|
2718
|
+
_nvals = 8*self.nzcombis*(2*self.nmaxs[0]+1)*(2*self.nmaxs[1]+1)*self.nbinsr**3
|
|
2719
|
+
if _nvals>cutlen:
|
|
2720
|
+
raise ValueError(("4pcf in multipole basis will cause memory overflow " +
|
|
2721
|
+
"(requiring %.2fx10^9 > %.2fx10^9 elements)\n"%(_nvals/1e9, cutlen/1e9) +
|
|
2722
|
+
"If you are solely interested in integrated statistics (like Map4), you" +
|
|
2723
|
+
"only need to add those to the `statistics` argument."))
|
|
2724
|
+
if "4pcf_real" in statistics:
|
|
2725
|
+
_nvals = 8*self.nzcombis*self.nbinsphi[0]*self.nbinsphi[1]*self.nbinsr**3
|
|
2726
|
+
if _nvals>cutlen:
|
|
2727
|
+
raise ValueError(("4pcf in real basis will cause memory overflow " +
|
|
2728
|
+
"(requiring %.2fx10^9 > %.2fx10^9 elements)\n"%(_nvals/1e9, cutlen/1e9) +
|
|
2729
|
+
"If you are solely interested in integrated statistics (like Map4), you" +
|
|
2730
|
+
"only need to add those to the `statistics` argument."))
|
|
2731
|
+
|
|
2732
|
+
# Decide on whether to use low-mem functions or not
|
|
2733
|
+
if hasintegratedstats:
|
|
2734
|
+
if lowmem in [False, None]:
|
|
2735
|
+
if not lowmem:
|
|
2736
|
+
print("Low-memory computation enforced for integrated measures of the 4pcf. " +
|
|
2737
|
+
"Set `lowmem` from `%s` to `True`"%str(lowmem))
|
|
2738
|
+
lowmem = True
|
|
2739
|
+
else:
|
|
2740
|
+
if lowmem in [None, False]:
|
|
2741
|
+
maxlen = 0
|
|
2742
|
+
_lowmem = False
|
|
2743
|
+
if "4pcf_multipole" in statistics:
|
|
2744
|
+
_nvals = 8*self.nzcombis*(2*self.nmaxs[0]+1)*(2*self.nmaxs[1]+1)*self.nbinsr**3
|
|
2745
|
+
if _nvals > cutlen:
|
|
2746
|
+
if not lowmem:
|
|
2747
|
+
print("Switching to low-memory computation of 4pcf in multipole basis.")
|
|
2748
|
+
lowmem = True
|
|
2749
|
+
else:
|
|
2750
|
+
lowmem = False
|
|
2751
|
+
if "4pcf_real" in statistics:
|
|
2752
|
+
nvals = 8*self.nzcombis*self.nbinsphi[0]*self.nbinsphi[1]*self.nbinsr**3
|
|
2753
|
+
if _nvals > cutlen:
|
|
2754
|
+
if not lowmem:
|
|
2755
|
+
print("Switching to low-memory computation of 4pcf in real basis.")
|
|
2756
|
+
lowmem = True
|
|
2757
|
+
else:
|
|
2758
|
+
lowmem = False
|
|
2759
|
+
|
|
2760
|
+
# Misc checks
|
|
2761
|
+
assert(projection in self.projections_avail)
|
|
2762
|
+
self._checkcats(cat, self.spins)
|
|
2763
|
+
i_projection = np.int32(self.proj_dict[projection])
|
|
2764
|
+
|
|
2765
|
+
## Build args for wrapped functions ##
|
|
2766
|
+
# Shortcuts
|
|
2767
|
+
_nmax = self.nmaxs[0]
|
|
2768
|
+
_nnvals = (2*_nmax+1)*(2*_nmax+1)
|
|
2769
|
+
_nbinsr3 = self.nbinsr*self.nbinsr*self.nbinsr
|
|
2770
|
+
_nphis = len(self.phis[0])
|
|
2771
|
+
sc = (8,2*_nmax+1,2*_nmax+1,self.nzcombis,self.nbinsr,self.nbinsr,self.nbinsr)
|
|
2772
|
+
sn = (2*_nmax+1,2*_nmax+1,self.nzcombis,self.nbinsr,self.nbinsr,self.nbinsr)
|
|
2773
|
+
szr = (self.nbinsz, self.nbinsr)
|
|
2774
|
+
s4pcf = (8,self.nzcombis,self.nbinsr,self.nbinsr,self.nbinsr,_nphis,_nphis)
|
|
2775
|
+
s4pcfn = (self.nzcombis,self.nbinsr,self.nbinsr,self.nbinsr,_nphis,_nphis)
|
|
2776
|
+
# Init default args
|
|
2777
|
+
bin_centers = np.zeros(self.nbinsz*self.nbinsr).astype(np.float64)
|
|
2778
|
+
if not cat.hasspatialhash:
|
|
2779
|
+
cat.build_spatialhash(dpix=max(1.,self.max_sep//10.))
|
|
2780
|
+
nregions = np.int32(len(np.argwhere(cat.index_matcher>-1).flatten()))
|
|
2781
|
+
args_basecat = (cat.isinner.astype(np.float64), cat.weight, cat.pos1, cat.pos2,
|
|
2782
|
+
cat.tracer_1, cat.tracer_2, np.int32(cat.ngal), )
|
|
2783
|
+
args_hash = (cat.index_matcher, cat.pixs_galind_bounds, cat.pix_gals, nregions,
|
|
2784
|
+
np.float64(cat.pix1_start), np.float64(cat.pix1_d), np.int32(cat.pix1_n),
|
|
2785
|
+
np.float64(cat.pix2_start), np.float64(cat.pix2_d), np.int32(cat.pix2_n), )
|
|
2786
|
+
|
|
2787
|
+
# Init optional args
|
|
2788
|
+
__lenflag = 10
|
|
2789
|
+
__fillflag = -1
|
|
2790
|
+
if "4pcf_multipole" in statistics:
|
|
2791
|
+
Upsilon_n = np.zeros(self.n_cfs*_nnvals*self.nzcombis*_nbinsr3).astype(np.complex128)
|
|
2792
|
+
N_n = np.zeros(_nnvals*self.nzcombis*_nbinsr3).astype(np.complex128)
|
|
2793
|
+
alloc_4pcfmultipoles = 1
|
|
2794
|
+
else:
|
|
2795
|
+
Upsilon_n = __fillflag*np.ones(__lenflag).astype(np.complex128)
|
|
2796
|
+
N_n = __fillflag*np.zeros(__lenflag).astype(np.complex128)
|
|
2797
|
+
alloc_4pcfmultipoles = 0
|
|
2798
|
+
if "4pcf_real" in statistics:
|
|
2799
|
+
fourpcf = np.zeros(8*_nphis*_nphis*self.nzcombis*_nbinsr3).astype(np.complex128)
|
|
2800
|
+
fourpcf_norm = np.zeros(_nphis*_nphis*self.nzcombis*_nbinsr3).astype(np.complex128)
|
|
2801
|
+
alloc_4pcfreal = 1
|
|
2802
|
+
else:
|
|
2803
|
+
fourpcf = __fillflag*np.ones(__lenflag).astype(np.complex128)
|
|
2804
|
+
fourpcf_norm = __fillflag*np.ones(__lenflag).astype(np.complex128)
|
|
2805
|
+
alloc_4pcfreal = 0
|
|
2806
|
+
if hasintegratedstats:
|
|
2807
|
+
if mapradii is None:
|
|
2808
|
+
raise ValueError("Aperture radii need to be specified in variable `mapradii`.")
|
|
2809
|
+
mapradii = mapradii.astype(np.float64)
|
|
2810
|
+
M4correlators = np.zeros(8*self.nzcombis*len(mapradii)).astype(np.complex128)
|
|
2811
|
+
else:
|
|
2812
|
+
mapradii = __fillflag*np.ones(__lenflag).astype(np.float64)
|
|
2813
|
+
N4correlators = __fillflag*np.ones(__lenflag).astype(np.complex128)
|
|
2814
|
+
|
|
2815
|
+
# Build args based on chosen methods
|
|
2816
|
+
if self.method=="Discrete" and not lowmem:
|
|
2817
|
+
args_basesetup = (np.int32(_nmax), np.float64(self.min_sep),
|
|
2818
|
+
np.float64(self.max_sep), np.array([-1.]).astype(np.float64),
|
|
2819
|
+
np.int32(self.nbinsr), np.int32(self.multicountcorr), )
|
|
2820
|
+
args = (*args_basecat,
|
|
2821
|
+
*args_basesetup,
|
|
2822
|
+
*args_hash,
|
|
2823
|
+
np.int32(self.nthreads),
|
|
2824
|
+
np.int32(self._verbose_c+self._verbose_debug),
|
|
2825
|
+
bin_centers,
|
|
2826
|
+
Upsilon_n,
|
|
2827
|
+
N_n)
|
|
2828
|
+
func = self.clib.alloc_notomoGammans_discrete_gggg
|
|
2829
|
+
if self.method=="Discrete" and lowmem:
|
|
2830
|
+
_resradial = gen_thetacombis_fourthorder(nbinsr=self.nbinsr, nthreads=self.nthreads, batchsize=batchsize,
|
|
2831
|
+
batchsize_max=self.thetabatchsize_max, ordered=True, custom=custom_thetacombis,
|
|
2832
|
+
verbose=self._verbose_python)
|
|
2833
|
+
_, _, thetacombis_batches, cumnthetacombis_batches, nthetacombis_batches, nbatches = _resradial
|
|
2834
|
+
|
|
2835
|
+
args_basesetup = (np.int32(_nmax),
|
|
2836
|
+
np.float64(self.min_sep), np.float64(self.max_sep), np.int32(self.nbinsr),
|
|
2837
|
+
np.int32(self.multicountcorr),
|
|
2838
|
+
self.phis[0].astype(np.float64),
|
|
2839
|
+
2*np.pi/_nphis*np.ones(_nphis, dtype=np.float64), np.int32(_nphis))
|
|
2840
|
+
args_4pcf = (np.int32(alloc_4pcfmultipoles), np.int32(alloc_4pcfreal),
|
|
2841
|
+
bin_centers, Upsilon_n, N_n, fourpcf, fourpcf_norm, )
|
|
2842
|
+
args_thetas = (thetacombis_batches, nthetacombis_batches, cumnthetacombis_batches, nbatches, )
|
|
2843
|
+
args_map4 = (mapradii, np.int32(len(mapradii)), M4correlators)
|
|
2844
|
+
args = (*args_basecat,
|
|
2845
|
+
*args_basesetup,
|
|
2846
|
+
*args_hash,
|
|
2847
|
+
*args_thetas,
|
|
2848
|
+
np.int32(self.nthreads),
|
|
2849
|
+
np.int32(self._verbose_c+self._verbose_debug),
|
|
2850
|
+
i_projection,
|
|
2851
|
+
*args_map4,
|
|
2852
|
+
*args_4pcf)
|
|
2853
|
+
func = self.clib.alloc_notomoMap4_disc_gggg
|
|
2854
|
+
if self.method=="Tree":
|
|
2855
|
+
# Prepare mask for nonredundant theta- and multipole configurations
|
|
2856
|
+
_resradial = gen_thetacombis_fourthorder(nbinsr=self.nbinsr, nthreads=self.nthreads, batchsize=batchsize,
|
|
2857
|
+
batchsize_max=self.thetabatchsize_max, ordered=True, custom=custom_thetacombis,
|
|
2858
|
+
verbose=self._verbose_python*lowmem)
|
|
2859
|
+
_, _, thetacombis_batches, cumnthetacombis_batches, nthetacombis_batches, nbatches = _resradial
|
|
2860
|
+
assert(self.nmaxs[0]==self.nmaxs[1])
|
|
2861
|
+
_resmultipoles = gen_n2n3indices_Upsfourth(self.nmaxs[0])
|
|
2862
|
+
_shape, _inds, _n2s, _n3s = _resmultipoles
|
|
2863
|
+
|
|
2864
|
+
# Prepare reduced catalogs
|
|
2865
|
+
cutfirst = np.int32(self.tree_resos[0]==0.)
|
|
2866
|
+
mhash = cat.multihash(dpixs=self.tree_resos[cutfirst:], dpix_hash=self.tree_resos[-1],
|
|
2867
|
+
shuffle=self.shuffle_pix, w2field=True, normed=True)
|
|
2868
|
+
(ngal_resos, pos1s, pos2s, weights, zbins, isinners, allfields,
|
|
2869
|
+
index_matchers, pixs_galind_bounds, pix_gals, dpixs1_true, dpixs2_true) = mhash
|
|
2870
|
+
weight_resos = np.concatenate(weights).astype(np.float64)
|
|
2871
|
+
pos1_resos = np.concatenate(pos1s).astype(np.float64)
|
|
2872
|
+
pos2_resos = np.concatenate(pos2s).astype(np.float64)
|
|
2873
|
+
zbin_resos = np.concatenate(zbins).astype(np.int32)
|
|
2874
|
+
isinner_resos = np.concatenate(isinners).astype(np.float64)
|
|
2875
|
+
e1_resos = np.concatenate([allfields[i][0] for i in range(len(allfields))]).astype(np.float64)
|
|
2876
|
+
e2_resos = np.concatenate([allfields[i][1] for i in range(len(allfields))]).astype(np.float64)
|
|
2877
|
+
index_matcher_resos = np.concatenate(index_matchers).astype(np.int32)
|
|
2878
|
+
pixs_galind_bounds_resos = np.concatenate(pixs_galind_bounds).astype(np.int32)
|
|
2879
|
+
pix_gals_resos = np.concatenate(pix_gals).astype(np.int32)
|
|
2880
|
+
index_matcher_flat = np.argwhere(cat.index_matcher>-1).flatten()
|
|
2881
|
+
nregions = len(index_matcher_flat)
|
|
2882
|
+
if not lowmem:
|
|
2883
|
+
args_basesetup = (np.int32(_nmax),
|
|
2884
|
+
np.float64(self.min_sep), np.float64(self.max_sep), np.int32(self.nbinsr),
|
|
2885
|
+
np.int32(cumnthetacombis_batches[-1]), np.int32(self.multicountcorr),
|
|
2886
|
+
_inds, np.int32(len(_inds)),)
|
|
2887
|
+
args_resos = (np.int32(self.tree_nresos), self.tree_redges, np.array(ngal_resos, dtype=np.int32),
|
|
2888
|
+
isinner_resos, weight_resos, pos1_resos, pos2_resos, e1_resos, e2_resos,
|
|
2889
|
+
index_matcher_resos, pixs_galind_bounds_resos, pix_gals_resos, np.int32(nregions), )
|
|
2890
|
+
args_hash = (np.float64(cat.pix1_start), np.float64(cat.pix1_d), np.int32(cat.pix1_n),
|
|
2891
|
+
np.float64(cat.pix2_start), np.float64(cat.pix2_d), np.int32(cat.pix2_n), )
|
|
2892
|
+
args_out = ( bin_centers, Upsilon_n, N_n, )
|
|
2893
|
+
args = (*args_basecat,
|
|
2894
|
+
*args_basesetup,
|
|
2895
|
+
*args_resos,
|
|
2896
|
+
*args_hash,
|
|
2897
|
+
np.int32(self.nthreads),
|
|
2898
|
+
np.int32(self._verbose_c+self._verbose_debug),
|
|
2899
|
+
*args_out)
|
|
2900
|
+
func = self.clib.alloc_notomoGammans_tree_gggg
|
|
2901
|
+
if lowmem:
|
|
2902
|
+
# Build args
|
|
2903
|
+
args_basesetup = (np.int32(_nmax),
|
|
2904
|
+
np.float64(self.min_sep), np.float64(self.max_sep), np.int32(self.nbinsr),
|
|
2905
|
+
np.int32(self.multicountcorr),
|
|
2906
|
+
_inds, np.int32(len(_inds)), self.phis[0].astype(np.float64),
|
|
2907
|
+
2*np.pi/_nphis*np.ones(_nphis, dtype=np.float64), np.int32(_nphis), )
|
|
2908
|
+
args_resos = (np.int32(self.tree_nresos), self.tree_redges, np.array(ngal_resos, dtype=np.int32),
|
|
2909
|
+
isinner_resos, weight_resos, pos1_resos, pos2_resos, e1_resos, e2_resos,
|
|
2910
|
+
index_matcher_resos, pixs_galind_bounds_resos, pix_gals_resos, np.int32(nregions), )
|
|
2911
|
+
args_hash = (np.float64(cat.pix1_start), np.float64(cat.pix1_d), np.int32(cat.pix1_n),
|
|
2912
|
+
np.float64(cat.pix2_start), np.float64(cat.pix2_d), np.int32(cat.pix2_n), )
|
|
2913
|
+
args_thetas = (thetacombis_batches, nthetacombis_batches, cumnthetacombis_batches, nbatches, )
|
|
2914
|
+
args_map4 = (mapradii, np.int32(len(mapradii)), M4correlators)
|
|
2915
|
+
args_4pcf = (np.int32(alloc_4pcfmultipoles), np.int32(alloc_4pcfreal),
|
|
2916
|
+
bin_centers, Upsilon_n, N_n, fourpcf, fourpcf_norm, )
|
|
2917
|
+
args = (*args_basecat,
|
|
2918
|
+
*args_basesetup,
|
|
2919
|
+
*args_resos,
|
|
2920
|
+
*args_hash,
|
|
2921
|
+
*args_thetas,
|
|
2922
|
+
np.int32(self.nthreads),
|
|
2923
|
+
np.int32(self._verbose_c+self._verbose_debug),
|
|
2924
|
+
i_projection,
|
|
2925
|
+
*args_map4,
|
|
2926
|
+
*args_4pcf)
|
|
2927
|
+
func = self.clib.alloc_notomoMap4_tree_gggg
|
|
2928
|
+
|
|
2929
|
+
# Optionally print the arguments
|
|
2930
|
+
if self._verbose_debug:
|
|
2931
|
+
print("We pass the following arguments:")
|
|
2932
|
+
for elarg, arg in enumerate(args):
|
|
2933
|
+
toprint = (elarg, type(arg),)
|
|
2934
|
+
if isinstance(arg, np.ndarray):
|
|
2935
|
+
toprint += (type(arg[0]), arg.shape)
|
|
2936
|
+
try:
|
|
2937
|
+
toprint += (func.argtypes[elarg], )
|
|
2938
|
+
print(toprint)
|
|
2939
|
+
print(arg)
|
|
2940
|
+
except:
|
|
2941
|
+
print("We did have a problem for arg %i"%elarg)
|
|
2942
|
+
|
|
2943
|
+
## Compute 4th order stats ##
|
|
2944
|
+
func(*args)
|
|
2945
|
+
self.projection = projection
|
|
2946
|
+
|
|
2947
|
+
## Massage the output ##
|
|
2948
|
+
istatout = ()
|
|
2949
|
+
self.bin_centers = bin_centers.reshape(szr)
|
|
2950
|
+
self.bin_centers_mean = np.mean(self.bin_centers, axis=0)
|
|
2951
|
+
if "4pcf_multipole" in statistics:
|
|
2952
|
+
self.npcf_multipoles = Upsilon_n.reshape(sc)
|
|
2953
|
+
self.npcf_multipoles_norm = N_n.reshape(sn)
|
|
2954
|
+
if "4pcf_real" in statistics:
|
|
2955
|
+
if lowmem:
|
|
2956
|
+
self.npcf = fourpcf.reshape(s4pcf)
|
|
2957
|
+
self.npcf_norm = fourpcf_norm.reshape(s4pcfn)
|
|
2958
|
+
else:
|
|
2959
|
+
if self._verbose_python:
|
|
2960
|
+
print("Transforming output to real space basis")
|
|
2961
|
+
self.multipoles2npcf_c(projection=projection)
|
|
2962
|
+
if hasintegratedstats:
|
|
2963
|
+
if "M4" in statistics:
|
|
2964
|
+
istatout += (M4correlators.reshape((8,self.nzcombis,len(mapradii))), )
|
|
2965
|
+
# TODO allocate map4, map4c etc.
|
|
2966
|
+
|
|
2967
|
+
return istatout
|
|
2968
|
+
|
|
2969
|
+
def multipoles2npcf_c(self, projection="X"):
|
|
2970
|
+
r""" Converts a 4PCF in the multipole basis in the real space basis.
|
|
2971
|
+
"""
|
|
2972
|
+
assert((projection in self.proj_dict.keys()) and (projection in self.projections_avail))
|
|
2973
|
+
|
|
2974
|
+
_nzero1 = self.nmaxs[0]
|
|
2975
|
+
_nzero2 = self.nmaxs[1]
|
|
2976
|
+
_phis1 = self.phis[0].astype(np.float64)
|
|
2977
|
+
_phis2 = self.phis[1].astype(np.float64)
|
|
2978
|
+
_nphis1 = len(self.phis[0])
|
|
2979
|
+
_nphis2 = len(self.phis[1])
|
|
2980
|
+
ncfs, nnvals, _, nzcombis, nbinsr, _, _ = np.shape(self.npcf_multipoles)
|
|
2981
|
+
|
|
2982
|
+
shape_npcf = (self.n_cfs, nzcombis, nbinsr, nbinsr, nbinsr, _nphis1, _nphis2)
|
|
2983
|
+
shape_npcf_norm = (nzcombis, nbinsr, nbinsr, nbinsr, _nphis1, _nphis2)
|
|
2984
|
+
self.npcf = np.zeros(self.n_cfs*nzcombis*nbinsr*nbinsr*nbinsr*_nphis1*_nphis2, dtype=np.complex128)
|
|
2985
|
+
self.npcf_norm = np.zeros(nzcombis*nbinsr*nbinsr*nbinsr*_nphis1*_nphis2, dtype=np.complex128)
|
|
2986
|
+
self.clib.multipoles2npcf_gggg(self.npcf_multipoles.flatten(), self.npcf_multipoles_norm.flatten(),
|
|
2987
|
+
self.bin_centers_mean.astype(np.float64), np.int32(self.proj_dict[projection]),
|
|
2988
|
+
8, nbinsr, self.nmaxs[0].astype(np.int32), _phis1, _nphis1, _phis2, _nphis2,
|
|
2989
|
+
self.nthreads, self.npcf, self.npcf_norm)
|
|
2990
|
+
self.npcf = self.npcf.reshape(shape_npcf)
|
|
2991
|
+
self.npcf_norm = self.npcf_norm.reshape(shape_npcf_norm)
|
|
2992
|
+
self.projection = projection
|
|
2993
|
+
|
|
2994
|
+
|
|
2995
|
+
def multipoles2npcf_singlethetcombi(self, elthet1, elthet2, elthet3, projection="X"):
|
|
2996
|
+
r""" Converts a 4PCF in the multipole basis in the real space basis for a fixed combination of radial bins.
|
|
2997
|
+
|
|
2998
|
+
Returns:
|
|
2999
|
+
--------
|
|
3000
|
+
npcf_out: np.ndarray
|
|
3001
|
+
Natural 4PCF components in the real-space bassi for all angular combinations.
|
|
3002
|
+
npcf_norm_out: np.ndarray
|
|
3003
|
+
4PCF weighted counts in the real-space bassi for all angular combinations.
|
|
3004
|
+
"""
|
|
3005
|
+
assert((projection in self.proj_dict.keys()) and (projection in self.projections_avail))
|
|
3006
|
+
|
|
3007
|
+
_phis1 = self.phis[0].astype(np.float64)
|
|
3008
|
+
_phis2 = self.phis[1].astype(np.float64)
|
|
3009
|
+
_nphis1 = len(self.phis[0])
|
|
3010
|
+
_nphis2 = len(self.phis[1])
|
|
3011
|
+
ncfs, nnvals, _, nzcombis, nbinsr, _, _ = np.shape(self.npcf_multipoles)
|
|
3012
|
+
|
|
3013
|
+
Upsilon_in = self.npcf_multipoles[...,elthet1,elthet2,elthet3].flatten()
|
|
3014
|
+
N_in = self.npcf_multipoles_norm[...,elthet1,elthet2,elthet3].flatten()
|
|
3015
|
+
npcf_out = np.zeros(self.n_cfs*nzcombis*_nphis1*_nphis2, dtype=np.complex128)
|
|
3016
|
+
npcf_norm_out = np.zeros(nzcombis*_nphis1*_nphis2, dtype=np.complex128)
|
|
3017
|
+
|
|
3018
|
+
self.clib.multipoles2npcf_gggg_singletheta(
|
|
3019
|
+
Upsilon_in, N_in, self.nmaxs[0], self.nmaxs[1],
|
|
3020
|
+
self.bin_centers_mean[elthet1], self.bin_centers_mean[elthet2], self.bin_centers_mean[elthet3],
|
|
3021
|
+
_phis1, _phis2, _nphis1, _nphis2,
|
|
3022
|
+
np.int32(self.proj_dict[projection]), npcf_out, npcf_norm_out)
|
|
3023
|
+
|
|
3024
|
+
return npcf_out.reshape((self.n_cfs, _nphis1,_nphis2)), npcf_norm_out.reshape((_nphis1,_nphis2))
|
|
3025
|
+
|
|
3026
|
+
def multipoles2npcf_gggg_singletheta_nconvergence(self, elthet1, elthet2, elthet3, projection="X"):
|
|
3027
|
+
r""" Checks convergence of the conversion between mutltipole-space and real space for a combination of radial bins.
|
|
3028
|
+
|
|
3029
|
+
Returns:
|
|
3030
|
+
--------
|
|
3031
|
+
npcf_out: np.ndarray
|
|
3032
|
+
Natural 4PCF components in the real-space basis for all angular combinations.
|
|
3033
|
+
npcf_norm_out: np.ndarray
|
|
3034
|
+
4PCF weighted counts in the real-space basis for all angular combinations.
|
|
3035
|
+
"""
|
|
3036
|
+
assert((projection in self.proj_dict.keys()) and (projection in self.projections_avail))
|
|
3037
|
+
|
|
3038
|
+
_phis1 = self.phis[0].astype(np.float64)
|
|
3039
|
+
_phis2 = self.phis[1].astype(np.float64)
|
|
3040
|
+
_nphis1 = len(self.phis[0])
|
|
3041
|
+
_nphis2 = len(self.phis[1])
|
|
3042
|
+
|
|
3043
|
+
ncfs, nnvals, _, nzcombis, nbinsr, _, _ = np.shape(self.npcf_multipoles)
|
|
3044
|
+
|
|
3045
|
+
Upsilon_in = self.npcf_multipoles[...,elthet1,elthet2,elthet3].flatten()
|
|
3046
|
+
N_in = self.npcf_multipoles_norm[...,elthet1,elthet2,elthet3].flatten()
|
|
3047
|
+
npcf_out = np.zeros(self.n_cfs*nzcombis*(self.nmaxs[0]+1)*(self.nmaxs[1]+1)*_nphis1*_nphis2, dtype=np.complex128)
|
|
3048
|
+
npcf_norm_out = np.zeros(nzcombis*(self.nmaxs[0]+1)*(self.nmaxs[1]+1)*_nphis1*_nphis2, dtype=np.complex128)
|
|
3049
|
+
|
|
3050
|
+
self.clib.multipoles2npcf_gggg_singletheta_nconvergence(
|
|
3051
|
+
Upsilon_in, N_in, self.nmaxs[0], self.nmaxs[1],
|
|
3052
|
+
self.bin_centers_mean[elthet1], self.bin_centers_mean[elthet2], self.bin_centers_mean[elthet3],
|
|
3053
|
+
_phis1, _phis2, _nphis1, _nphis2,
|
|
3054
|
+
np.int32(self.proj_dict[projection]), npcf_out, npcf_norm_out)
|
|
3055
|
+
|
|
3056
|
+
npcf_out = npcf_out.reshape((self.n_cfs, self.nmaxs[0]+1, self.nmaxs[1]+1, _nphis1, _nphis2))
|
|
3057
|
+
npcf_norm_out = npcf_norm_out.reshape((self.nmaxs[0]+1, self.nmaxs[1]+1, _nphis1, _nphis2))
|
|
3058
|
+
|
|
3059
|
+
return npcf_out, npcf_norm_out
|
|
3060
|
+
|
|
3061
|
+
def computeMap4(self, radii, nmax_trafo=None, basis='MapMx'):
|
|
3062
|
+
r"""Computes the fourth-order aperture mass statistcs using the polynomial filter of Crittenden 2002."""
|
|
3063
|
+
|
|
3064
|
+
assert(basis in ['MapMx','MM*','both'])
|
|
3065
|
+
|
|
3066
|
+
if nmax_trafo is None:
|
|
3067
|
+
nmax_trafo=self.nmaxs[0]
|
|
3068
|
+
|
|
3069
|
+
# Retrieve all the aperture measures in the MM* basis via the 5D transformation eqns
|
|
3070
|
+
M4correlators = np.zeros(8*len(radii), dtype=np.complex128)
|
|
3071
|
+
self.clib.fourpcfmultipoles2M4correlators(
|
|
3072
|
+
np.int32(self.nmaxs[0]), np.int32(nmax_trafo),
|
|
3073
|
+
self.bin_edges, self.bin_centers_mean, np.int32(self.nbinsr),
|
|
3074
|
+
radii.astype(np.float64), np.int32(len(radii)),
|
|
3075
|
+
self.phis[0].astype(np.float64), self.phis[1].astype(np.float64),
|
|
3076
|
+
self.dphis[0].astype(np.float64), self.dphis[1].astype(np.float64),
|
|
3077
|
+
len(self.phis[0]), len(self.phis[1]),
|
|
3078
|
+
np.int32(self.proj_dict[self.projection]), np.int32(self.nthreads),
|
|
3079
|
+
self.npcf_multipoles.flatten(), self.npcf_multipoles_norm.flatten(),
|
|
3080
|
+
M4correlators)
|
|
3081
|
+
res_MMStar = M4correlators.reshape((8,len(radii)))
|
|
3082
|
+
|
|
3083
|
+
# Allocate result
|
|
3084
|
+
res = ()
|
|
3085
|
+
if basis=='MM*' or basis=='both':
|
|
3086
|
+
res += (res_MMStar, )
|
|
3087
|
+
if basis=='MapMx' or basis=='both':
|
|
3088
|
+
res += ( GGGGCorrelation_NoTomo.MMStar2MapMx_fourth(res_MMStar), )
|
|
3089
|
+
|
|
3090
|
+
return res
|
|
3091
|
+
|
|
3092
|
+
## PROJECTIONS ##
|
|
3093
|
+
def projectnpcf(self, projection):
|
|
3094
|
+
super()._projectnpcf(self, projection)
|
|
3095
|
+
|
|
3096
|
+
def _x2centroid(self):
|
|
3097
|
+
gammas_cen = np.zeros_like(self.npcf)
|
|
3098
|
+
pimod = lambda x: x%(2*np.pi) - 2*np.pi*(x%(2*np.pi)>=np.pi)
|
|
3099
|
+
npcf_cen = np.zeros(self.npcf.shape, dtype=complex)
|
|
3100
|
+
_centers = np.mean(self.bin_centers, axis=0)
|
|
3101
|
+
for elb1, bin1 in enumerate(_centers):
|
|
3102
|
+
for elb2, bin2 in enumerate(_centers):
|
|
3103
|
+
for elb3, bin3 in enumerate(_centers):
|
|
3104
|
+
phiexp = np.exp(1J*self.phis[0])
|
|
3105
|
+
phiexp_c = np.exp(-1J*self.phis[0])
|
|
3106
|
+
phi12grid, phi13grid = np.meshgrid(phiexp, phiexp)
|
|
3107
|
+
phi12grid_c, phi13grid_c = np.meshgrid(phiexp_c, phiexp_c)
|
|
3108
|
+
prod1 = (bin1 +bin2*phi12grid_c + bin3*phi13grid_c) /(bin1 + bin2*phi12grid + bin3*phi13grid) #q1
|
|
3109
|
+
prod2 = (3*bin1 -bin2*phi12grid_c - bin3*phi13grid_c) /(3*bin1 - bin2*phi12grid - bin3*phi13grid) #q2
|
|
3110
|
+
prod3 = (bin1 -3*bin2*phi12grid_c + bin3*phi13grid_c) /(bin1 - 3*bin2*phi12grid + bin3*phi13grid) #q3
|
|
3111
|
+
prod4 = (bin1 +bin2*phi12grid_c - 3*bin3*phi13grid_c)/(bin1 + bin2*phi12grid - 3*bin3*phi13grid) #q4
|
|
3112
|
+
prod1_inv = prod1.conj()/np.abs(prod1)
|
|
3113
|
+
prod2_inv = prod2.conj()/np.abs(prod2)
|
|
3114
|
+
prod3_inv = prod3.conj()/np.abs(prod3)
|
|
3115
|
+
prod4_inv = prod4.conj()/np.abs(prod4)
|
|
3116
|
+
rot_nom = np.zeros((8,len(self.phis[0]), len(self.phis[1])))
|
|
3117
|
+
rot_nom[0] = pimod(np.angle(prod1 *prod2 *prod3 *prod4 * phi12grid**2 * phi13grid**3))
|
|
3118
|
+
rot_nom[1] = pimod(np.angle(prod1_inv*prod2 *prod3 *prod4 * phi12grid**2 * phi13grid**1))
|
|
3119
|
+
rot_nom[2] = pimod(np.angle(prod1 *prod2_inv*prod3 *prod4 * phi12grid**2 * phi13grid**3))
|
|
3120
|
+
rot_nom[3] = pimod(np.angle(prod1 *prod2 *prod3_inv*prod4 * phi12grid_c**2 * phi13grid**3))
|
|
3121
|
+
rot_nom[4] = pimod(np.angle(prod1 *prod2 *prod3 *prod4_inv * phi12grid**2 * phi13grid_c**1))
|
|
3122
|
+
rot_nom[5] = pimod(np.angle(prod1_inv*prod2_inv*prod3 *prod4 * phi12grid**2 * phi13grid**1))
|
|
3123
|
+
rot_nom[6] = pimod(np.angle(prod1_inv*prod2 *prod3_inv*prod4 * phi12grid_c**2 * phi13grid**1))
|
|
3124
|
+
rot_nom[7] = pimod(np.angle(prod1_inv*prod2 *prod3 *prod4_inv * phi12grid**2 * phi13grid_c**3))
|
|
3125
|
+
gammas_cen[:,:,elb1,elb2,elb3] = self.npcf[:,:,elb1,elb2,elb3]*np.exp(1j*rot_nom)[:,np.newaxis,:,:]
|
|
3126
|
+
return gammas_cen
|
|
3127
|
+
|
|
3128
|
+
## GAUSSIAN-FIELD SPECIFIC FUNCTIONS ##
|
|
3129
|
+
# Deprecate this as it has been ported to c
|
|
3130
|
+
@staticmethod
|
|
3131
|
+
def fourpcf_gauss_x(theta1, theta2, theta3, phi12, phi13, xipspl, ximspl):
|
|
3132
|
+
""" Computes disconnected part of the 4pcf in the 'x'-projection
|
|
3133
|
+
given a splined 2pcf
|
|
3134
|
+
"""
|
|
3135
|
+
allgammas = [None]*8
|
|
3136
|
+
xprojs = [None]*8
|
|
3137
|
+
y1 = theta1 * np.ones_like(phi12)
|
|
3138
|
+
y2 = theta2*np.exp(1j*phi12)
|
|
3139
|
+
y3 = theta3*np.exp(1j*phi13)
|
|
3140
|
+
absy1 = np.abs(y1)
|
|
3141
|
+
absy2 = np.abs(y2)
|
|
3142
|
+
absy3 = np.abs(y3)
|
|
3143
|
+
absy12 = np.abs(y2-y1)
|
|
3144
|
+
absy13 = np.abs(y1-y3)
|
|
3145
|
+
absy23 = np.abs(y3-y2)
|
|
3146
|
+
q1 = -0.25*(y1+y2+y3)
|
|
3147
|
+
q2 = 0.25*(3*y1-y2-y3)
|
|
3148
|
+
q3 = 0.25*(3*y2-y3-y1)
|
|
3149
|
+
q4 = 0.25*(3*y3-y1-y2)
|
|
3150
|
+
q1c = q1.conj(); q2c = q2.conj(); q3c = q3.conj(); q4c = q4.conj();
|
|
3151
|
+
y123_cub = (np.abs(y1)*np.abs(y2)*np.abs(y3))**3
|
|
3152
|
+
ang1_4 = ((y1)/absy1)**4; ang2_4 = ((y2)/absy2)**4; ang3_4 = ((y3)/absy3)**4
|
|
3153
|
+
ang12_4 = ((y2-y1)/absy12)**4; ang13_4 = ((y3-y1)/absy13)**4; ang23_4 = ((y3-y2)/absy23)**4;
|
|
3154
|
+
xprojs[0] = (y1**3*y2**2*y3**3)/(np.abs(y1)**3*np.abs(y2)**2*np.abs(y3)**3)
|
|
3155
|
+
xprojs[1] = (y1**1*y2**2*y3**1)/(np.abs(y1)**1*np.abs(y2)**2*np.abs(y3)**1)
|
|
3156
|
+
xprojs[2] = (y1**-1*y2**2*y3**3)/(np.abs(y1)**-1*np.abs(y2)**2*np.abs(y3)**3)
|
|
3157
|
+
xprojs[3] = (y1**3*y2**-2*y3**3)/(np.abs(y1)**3*np.abs(y2)**-2*np.abs(y3)**3)
|
|
3158
|
+
xprojs[4] = (y1**3*y2**2*y3**-1)/(np.abs(y1)**3*np.abs(y2)**2*np.abs(y3)**-1)
|
|
3159
|
+
xprojs[5] = (y1**-3*y2**2*y3**1)/(np.abs(y1)**-3*np.abs(y2)**2*np.abs(y3)**1)
|
|
3160
|
+
xprojs[6] = (y1**1*y2**-2*y3**1)/(np.abs(y1)**1*np.abs(y2)**-2*np.abs(y3)**1)
|
|
3161
|
+
xprojs[7] = (y1**1*y2**2*y3**-3)/(np.abs(y1)**1*np.abs(y2)**2*np.abs(y3)**-3)
|
|
3162
|
+
allgammas[0] = 1./xprojs[0] * (
|
|
3163
|
+
ang23_4 * ang1_4 * ximspl(absy23) * ximspl(absy1) +
|
|
3164
|
+
ang13_4 * ang2_4 * ximspl(absy13) * ximspl(absy2) +
|
|
3165
|
+
ang12_4 * ang3_4 * ximspl(absy12) * ximspl(absy3))
|
|
3166
|
+
allgammas[1] = 1./xprojs[1] * (
|
|
3167
|
+
ang23_4 * xipspl(absy1) * ximspl(absy23) +
|
|
3168
|
+
ang13_4 * xipspl(absy2) * ximspl(absy13) +
|
|
3169
|
+
ang12_4 * xipspl(absy3) * ximspl(absy12))
|
|
3170
|
+
allgammas[2] = 1./xprojs[2] * (
|
|
3171
|
+
ang23_4 * xipspl(absy1) * ximspl(absy23) +
|
|
3172
|
+
ang2_4 * ximspl(absy2) * xipspl(absy13) +
|
|
3173
|
+
ang3_4 * ximspl(absy3) * xipspl(absy12))
|
|
3174
|
+
allgammas[3] = 1./xprojs[3] * (
|
|
3175
|
+
ang1_4 * ximspl(absy1) * xipspl(absy23) +
|
|
3176
|
+
ang13_4 * xipspl(absy2) * ximspl(absy13) +
|
|
3177
|
+
ang3_4 * ximspl(absy3) * xipspl(absy12))
|
|
3178
|
+
allgammas[4] = 1./xprojs[4] * (
|
|
3179
|
+
ang1_4 * ximspl(absy1) * xipspl(absy23) +
|
|
3180
|
+
ang2_4 * ximspl(absy2) * xipspl(absy13) +
|
|
3181
|
+
ang12_4 * xipspl(absy3) * ximspl(absy12))
|
|
3182
|
+
allgammas[5] = 1./xprojs[5] * (
|
|
3183
|
+
ang1_4.conj() * ang23_4 * ximspl(absy23) * ximspl(absy1) +
|
|
3184
|
+
xipspl(absy13) * xipspl(absy2) +
|
|
3185
|
+
xipspl(absy12) * xipspl(absy3))
|
|
3186
|
+
allgammas[6] = 1./xprojs[6] * (
|
|
3187
|
+
xipspl(absy23) * xipspl(absy1) +
|
|
3188
|
+
ang2_4.conj() * ang13_4 * ximspl(absy13) * ximspl(absy2) +
|
|
3189
|
+
xipspl(absy12) * xipspl(absy3))
|
|
3190
|
+
allgammas[7] = 1./xprojs[7] * (
|
|
3191
|
+
xipspl(absy23) * xipspl(absy1) +
|
|
3192
|
+
xipspl(absy13) * xipspl(absy2) +
|
|
3193
|
+
ang3_4.conj() * ang12_4 * ximspl(absy12) * ximspl(absy3))
|
|
3194
|
+
|
|
3195
|
+
return allgammas
|
|
3196
|
+
|
|
3197
|
+
# Disconnected 4pcf from binned 2pcf (might want to deprecate this as it is a special case of nsubr==1)
|
|
3198
|
+
def __gauss4pcf_analytic(self, theta1, theta2, theta3, xip_arr, xim_arr, thetamin_xi, thetamax_xi, dtheta_xi):
|
|
3199
|
+
gausss_4pcf = np.zeros(8*len(self.phis[0])*len(self.phis[0]),dtype=np.complex128)
|
|
3200
|
+
self.clib.gauss4pcf_analytic(theta1.astype(np.float64),
|
|
3201
|
+
theta2.astype(np.float64),
|
|
3202
|
+
theta3.astype(np.float64),
|
|
3203
|
+
self.phis[0].astype(np.float64), np.int32(len(self.phis[0])),
|
|
3204
|
+
xip_arr.astype(np.float64), xim_arr.astype(np.float64),
|
|
3205
|
+
thetamin_xi, thetamax_xi, dtheta_xi,
|
|
3206
|
+
gausss_4pcf)
|
|
3207
|
+
return gausss_4pcf
|
|
3208
|
+
|
|
3209
|
+
|
|
3210
|
+
# [Debug] Disconnected 4pcf from analytic 2pcf
|
|
3211
|
+
def gauss4pcf_analytic(self, itheta1, itheta2, itheta3, nsubr,
|
|
3212
|
+
xip_arr, xim_arr, thetamin_xi, thetamax_xi, dtheta_xi):
|
|
3213
|
+
|
|
3214
|
+
gauss_4pcf = np.zeros(8*self.nbinsphi[0]*self.nbinsphi[1],dtype=np.complex128)
|
|
3215
|
+
|
|
3216
|
+
self.clib.gauss4pcf_analytic_integrated(
|
|
3217
|
+
np.int32(itheta1),
|
|
3218
|
+
np.int32(itheta2),
|
|
3219
|
+
np.int32(itheta3),
|
|
3220
|
+
np.int32(nsubr),
|
|
3221
|
+
self.bin_edges.astype(np.float64),
|
|
3222
|
+
np.int32(self.nbinsr),
|
|
3223
|
+
self.phis[0].astype(np.float64),
|
|
3224
|
+
np.int32(self.nbinsphi[0]),
|
|
3225
|
+
xip_arr.astype(np.float64),
|
|
3226
|
+
xim_arr.astype(np.float64),
|
|
3227
|
+
np.float64(thetamin_xi),
|
|
3228
|
+
np.float64(thetamax_xi),
|
|
3229
|
+
np.float64(dtheta_xi),
|
|
3230
|
+
gauss_4pcf)
|
|
3231
|
+
return gauss_4pcf.reshape((8, self.nbinsphi[0], self.nbinsphi[1]))
|
|
3232
|
+
|
|
3233
|
+
# Compute disconnected part of 4pcf in multiple basis
|
|
3234
|
+
def gauss4pcf_multipolebasis(self, itheta1, itheta2, itheta3, nsubr,
|
|
3235
|
+
xip_arr, xim_arr, thetamin_xi, thetamax_xi, dtheta_xi):
|
|
3236
|
+
|
|
3237
|
+
# Obtain integrated 4pcf
|
|
3238
|
+
int_4pcf = self.gauss4pcf_analytic_integrated(itheta1, itheta2, itheta3, nsubr,
|
|
3239
|
+
xip_arr, xim_arr,
|
|
3240
|
+
thetamin_xi, thetamax_xi, dtheta_xi)
|
|
3241
|
+
|
|
3242
|
+
# Transform to multiple basis (cf eq xxx in P25)
|
|
3243
|
+
phigrid1, phigrid2 = np.meshgrid(self.phis[0],self.phis[1])
|
|
3244
|
+
gauss_multipoles = np.zeros((8,2*self.nmaxs[0]+1,2*self.nmaxs[1]+1),dtype=complex)
|
|
3245
|
+
for eln2,n2 in enumerate(np.arange(-self.nmaxs[0],self.nmaxs[0]+1)):
|
|
3246
|
+
fac1 = np.e**(-1J*n2*phigrid1)
|
|
3247
|
+
for eln3,n3 in enumerate(np.arange(-self.nmaxs[1],self.nmaxs[1]+1)):
|
|
3248
|
+
fac2 = np.e**(-1J*n3*phigrid2)
|
|
3249
|
+
for elcomp in range(8):
|
|
3250
|
+
gauss_multipoles[elcomp,eln2,eln3] = np.mean(int_4pcf[elcomp]*fac1*fac2)
|
|
3251
|
+
|
|
3252
|
+
return gauss_multipoles
|
|
3253
|
+
|
|
3254
|
+
|
|
3255
|
+
def estimateMap4disc(self, cat, radii, basis='MapMx',fac_minsep=0.05, fac_maxsep=2., binsize=0.1, nsubr=3, nsubsample_filter=1):
|
|
3256
|
+
""" Estimate disconnected part of fourth-order aperture statistics on a shape catalog. """
|
|
3257
|
+
|
|
3258
|
+
# Compute shear 2pcf from data
|
|
3259
|
+
min_sep_disc = fac_minsep*self.min_sep
|
|
3260
|
+
max_sep_disc = fac_maxsep*self.max_sep
|
|
3261
|
+
binsize_disc = min(0.1,self.binsize)
|
|
3262
|
+
ggcorr = GGCorrelation(min_sep=min_sep_disc, max_sep=max_sep_disc,binsize=binsize_disc,
|
|
3263
|
+
rmin_pixsize=self.rmin_pixsize, tree_resos=self.tree_resos, nthreads=self.nthreads)
|
|
3264
|
+
ggcorr.process(cat)
|
|
3265
|
+
|
|
3266
|
+
# Convert this to fourth-order aperture statistics
|
|
3267
|
+
linarr = np.linspace(min_sep_disc,max_sep_disc,int(max_sep_disc/(binsize_disc*min_sep_disc)))
|
|
3268
|
+
xip_spl = interp1d(x=ggcorr.bin_centers_mean,y=ggcorr.xip[0].real,fill_value=0,bounds_error=False)
|
|
3269
|
+
xim_spl = interp1d(x=ggcorr.bin_centers_mean,y=ggcorr.xim[0].real,fill_value=0,bounds_error=False)
|
|
3270
|
+
mapstat = self.Map4analytic(mapradii=radii,
|
|
3271
|
+
xip_spl=xip_spl,
|
|
3272
|
+
xim_spl=xim_spl,
|
|
3273
|
+
thetamin_xi=linarr[0],
|
|
3274
|
+
thetamax_xi=linarr[-1],
|
|
3275
|
+
ntheta_xi=len(linarr),
|
|
3276
|
+
nsubr=nsubr,nsubsample_filter=nsubsample_filter,basis=basis)
|
|
3277
|
+
return mapstat
|
|
3278
|
+
|
|
3279
|
+
|
|
3280
|
+
# Disconnected part of Map^4 from analytic 2pcf
|
|
3281
|
+
# thetamin_xi, thetamax_xi, ntheta_xi is the linspaced array in which the xipm are passed to the external function
|
|
3282
|
+
def Map4analytic(self, mapradii, xip_spl, xim_spl, thetamin_xi, thetamax_xi, ntheta_xi,
|
|
3283
|
+
nsubr=1, nsubsample_filter=1, batchsize=None, basis='MapMx'):
|
|
3284
|
+
|
|
3285
|
+
self.nbinsz = 1
|
|
3286
|
+
self.nzcombis = 1
|
|
3287
|
+
_nmax = self.nmaxs[0]
|
|
3288
|
+
_nnvals = (2*_nmax+1)*(2*_nmax+1)
|
|
3289
|
+
_nbinsr3 = self.nbinsr*self.nbinsr*self.nbinsr
|
|
3290
|
+
_nphis = len(self.phis[0])
|
|
3291
|
+
bin_centers = np.zeros(self.nbinsz*self.nbinsr).astype(np.float64)
|
|
3292
|
+
M4correlators = np.zeros(8*self.nzcombis*len(mapradii)).astype(np.complex128)
|
|
3293
|
+
# Define the radial bin batches
|
|
3294
|
+
if batchsize is None:
|
|
3295
|
+
batchsize = min(_nbinsr3,min(10000,int(_nbinsr3/self.nthreads)))
|
|
3296
|
+
if self._verbose_python:
|
|
3297
|
+
print("Using batchsize of %i for radial bins"%batchsize)
|
|
3298
|
+
nbatches = np.int32(_nbinsr3/batchsize)
|
|
3299
|
+
thetacombis_batches = np.arange(_nbinsr3).astype(np.int32)
|
|
3300
|
+
cumnthetacombis_batches = (np.arange(nbatches+1)*_nbinsr3/(nbatches)).astype(np.int32)
|
|
3301
|
+
nthetacombis_batches = (cumnthetacombis_batches[1:]-cumnthetacombis_batches[:-1]).astype(np.int32)
|
|
3302
|
+
cumnthetacombis_batches[-1] = _nbinsr3
|
|
3303
|
+
nthetacombis_batches[-1] = _nbinsr3-cumnthetacombis_batches[-2]
|
|
3304
|
+
thetacombis_batches = thetacombis_batches.flatten().astype(np.int32)
|
|
3305
|
+
nbatches = len(nthetacombis_batches)
|
|
3306
|
+
|
|
3307
|
+
args_4pcfsetup = (np.float64(self.min_sep), np.float64(self.max_sep), np.int32(self.nbinsr),
|
|
3308
|
+
self.phis[0].astype(np.float64),
|
|
3309
|
+
(self.phis[0][1]-self.phis[0][0])*np.ones(_nphis, dtype=np.float64), _nphis, np.int32(nsubr), )
|
|
3310
|
+
args_thetas = (thetacombis_batches, nthetacombis_batches, cumnthetacombis_batches, nbatches, )
|
|
3311
|
+
args_map4 = (mapradii.astype(np.float64), np.int32(len(mapradii)), )
|
|
3312
|
+
thetas_xi = np.linspace(thetamin_xi,thetamax_xi,ntheta_xi+1)
|
|
3313
|
+
args_xi = (xip_spl(thetas_xi), xim_spl(thetas_xi), thetamin_xi, thetamax_xi, ntheta_xi, nsubsample_filter, )
|
|
3314
|
+
args = (*args_4pcfsetup,
|
|
3315
|
+
*args_thetas,
|
|
3316
|
+
np.int32(self.nthreads),
|
|
3317
|
+
*args_map4,
|
|
3318
|
+
*args_xi,
|
|
3319
|
+
M4correlators)
|
|
3320
|
+
func = self.clib.alloc_notomoMap4_analytic
|
|
3321
|
+
|
|
3322
|
+
if self._verbose_debug:
|
|
3323
|
+
for elarg, arg in enumerate(args):
|
|
3324
|
+
toprint = (elarg, type(arg),)
|
|
3325
|
+
if isinstance(arg, np.ndarray):
|
|
3326
|
+
toprint += (type(arg[0]), arg.shape)
|
|
3327
|
+
try:
|
|
3328
|
+
toprint += (func.argtypes[elarg], )
|
|
3329
|
+
print(toprint)
|
|
3330
|
+
print(arg)
|
|
3331
|
+
except:
|
|
3332
|
+
print("We did have a problem for arg %i"%elarg)
|
|
3333
|
+
|
|
3334
|
+
func(*args)
|
|
3335
|
+
|
|
3336
|
+
res_MMStar = M4correlators.reshape((8,len(mapradii)))
|
|
3337
|
+
# Allocate result
|
|
3338
|
+
res = ()
|
|
3339
|
+
if basis=='MM*' or basis=='both':
|
|
3340
|
+
res += (res_MMStar, )
|
|
3341
|
+
if basis=='MapMx' or basis=='both':
|
|
3342
|
+
res += (GGGGCorrelation_NoTomo.MMStar2MapMx_fourth(res_MMStar), )
|
|
3343
|
+
|
|
3344
|
+
return res
|
|
3345
|
+
|
|
3346
|
+
def getMultipolesFromSymm(self, nmax_rec, itheta1, itheta2, itheta3, eltrafo):
|
|
3347
|
+
|
|
3348
|
+
nmax_alloc = 2*nmax_rec+1
|
|
3349
|
+
assert(nmax_alloc<=self.nmaxs[0])
|
|
3350
|
+
|
|
3351
|
+
# Only select relevant n1/n2 indices
|
|
3352
|
+
_dn = self.nmaxs[0]-nmax_alloc
|
|
3353
|
+
|
|
3354
|
+
_shape, _inds, _n2s, _n3s = gen_n2n3indices_Upsfourth(nmax_rec)
|
|
3355
|
+
Upsn_in = self.npcf_multipoles[:,_dn:-_dn,_dn:-_dn,0,itheta1,itheta2,itheta3].flatten()
|
|
3356
|
+
Nn_in = self.npcf_multipoles_norm[_dn:-_dn,_dn:-_dn,0,itheta1,itheta2,itheta3].flatten()
|
|
3357
|
+
Upsn_out = np.zeros(8*(2*nmax_rec+1)*(2*nmax_rec+1), dtype=np.complex128)
|
|
3358
|
+
Nn_out = np.zeros(1*(2*nmax_rec+1)*(2*nmax_rec+1), dtype=np.complex128)
|
|
3359
|
+
|
|
3360
|
+
self.clib.getMultipolesFromSymm(
|
|
3361
|
+
Upsn_in, Nn_in, nmax_rec, eltrafo, _inds, len(_inds), Upsn_out, Nn_out)
|
|
3362
|
+
|
|
3363
|
+
Upsn_out = Upsn_out.reshape((8,(2*nmax_rec+1),(2*nmax_rec+1)))
|
|
3364
|
+
Nn_out = Nn_out.reshape(((2*nmax_rec+1),(2*nmax_rec+1)))
|
|
3365
|
+
|
|
3366
|
+
return Upsn_out, Nn_out
|
|
3367
|
+
|
|
3368
|
+
## MISC HELPERS ##
|
|
3369
|
+
@staticmethod
|
|
3370
|
+
def MMStar2MapMx_fourth(res_MMStar):
|
|
3371
|
+
""" Transforms fourth-order aperture correlators to fourth-order aperture mass.
|
|
3372
|
+
See i.e. Eqs (32)-(36) in Silvestre-Rosello+ 2025 (arxiv.org/pdf/2509.07973).
|
|
3373
|
+
"""
|
|
3374
|
+
res_MapMx = np.zeros((16,*res_MMStar.shape[1:]))
|
|
3375
|
+
Mcorr2Map4_re = .125*np.array([[+1,+1,+1,+1,+1,+1,+1,+1],
|
|
3376
|
+
[-1,-1,-1,+1,+1,-1,+1,+1],
|
|
3377
|
+
[-1,-1,+1,-1,+1,+1,-1,+1],
|
|
3378
|
+
[-1,-1,+1,+1,-1,+1,+1,-1],
|
|
3379
|
+
[-1,+1,-1,-1,+1,+1,+1,-1],
|
|
3380
|
+
[-1,+1,-1,+1,-1,+1,-1,+1],
|
|
3381
|
+
[-1,+1,+1,-1,-1,-1,+1,+1],
|
|
3382
|
+
[+1,-1,-1,-1,-1,+1,+1,+1]])
|
|
3383
|
+
Mcorr2Map4_im = .125*np.array([[+1,-1,+1,+1,+1,-1,-1,-1],
|
|
3384
|
+
[+1,+1,-1,+1,+1,-1,+1,+1],
|
|
3385
|
+
[+1,+1,+1,-1,+1,+1,-1,+1],
|
|
3386
|
+
[+1,+1,+1,+1,-1,+1,+1,-1],
|
|
3387
|
+
[-1,-1,+1,+1,+1,+1,+1,+1],
|
|
3388
|
+
[-1,+1,-1,+1,+1,+1,-1,-1],
|
|
3389
|
+
[-1,+1,+1,-1,+1,-1,+1,-1],
|
|
3390
|
+
[-1,+1,+1,+1,-1,-1,-1,+1]])
|
|
3391
|
+
res_MapMx[[0,5,6,7,8,9,10,15]] = Mcorr2Map4_re@(res_MMStar.real)
|
|
3392
|
+
res_MapMx[[1,2,3,4,11,12,13,14]] = Mcorr2Map4_im@(res_MMStar.imag)
|
|
3393
|
+
return res_MapMx
|
|
3394
|
+
|
|
3395
|
+
|
|
3396
|
+
class NNNNCorrelation_NoTomo(BinnedNPCF):
|
|
3397
|
+
r""" Class containing methods to measure and and obtain statistics that are built
|
|
3398
|
+
from nontomographic fourth-order scalar correlation functions.
|
|
3399
|
+
|
|
3400
|
+
Attributes
|
|
3401
|
+
----------
|
|
3402
|
+
min_sep: float
|
|
3403
|
+
The smallest distance of each vertex for which the NPCF is computed.
|
|
3404
|
+
max_sep: float
|
|
3405
|
+
The largest distance of each vertex for which the NPCF is computed.
|
|
3406
|
+
thetabatchsize_max: int, optional
|
|
3407
|
+
The largest number of radial bin combinations that are processed in parallel.
|
|
3408
|
+
Defaults to ``10 000``.
|
|
3409
|
+
|
|
3410
|
+
Notes
|
|
3411
|
+
-----
|
|
3412
|
+
Inherits all other parameters and attributes from :class:`BinnedNPCF`.
|
|
3413
|
+
Additional child-specific parameters can be passed via ``kwargs``.
|
|
3414
|
+
Either ``nbinsr`` or ``binsize`` has to be provided to fix the binning scheme .
|
|
3415
|
+
|
|
3416
|
+
"""
|
|
3417
|
+
|
|
3418
|
+
def __init__(self, min_sep, max_sep, verbose=False, thetabatchsize_max=10000, method="Tree", **kwargs):
|
|
3419
|
+
super().__init__(order=4, spins=np.array([0,0,0,0], dtype=np.int32),
|
|
3420
|
+
n_cfs=1, min_sep=min_sep, max_sep=max_sep,
|
|
3421
|
+
method=method, methods_avail=["Tree"], **kwargs)
|
|
3422
|
+
|
|
3423
|
+
self.thetabatchsize_max = thetabatchsize_max
|
|
3424
|
+
self.nbinsz = 1
|
|
3425
|
+
self.nzcombis = 1
|
|
3426
|
+
|
|
3427
|
+
def process(self, cat, statistics="all", tofile=False, apply_edge_correction=False,
|
|
3428
|
+
lowmem=True, mapradii=None, batchsize=None, custom_thetacombis=None, cutlen=2**31-1):
|
|
3429
|
+
r"""
|
|
3430
|
+
Arguments:
|
|
3431
|
+
|
|
3432
|
+
Logic works as follows:
|
|
3433
|
+
* Keyword 'statistics' \in [4pcf_real, 4pcf_multipoles, N4, Nap4, Nap4, Nap4c, allNap, all4pcf, all]
|
|
3434
|
+
* - If 4pcf_multipoles in statistics --> save 4pcf_multipoles
|
|
3435
|
+
* - If 4pcf_real in statistics --> save 4pcf_real
|
|
3436
|
+
* - If only N4 in statistics --> Do not save any 4pcf. This is really the lowmem case.
|
|
3437
|
+
* - allNap, all4pcf, all are abbreviations as expected
|
|
3438
|
+
* If lowmem=True, uses the inefficient, but lowmem function for computation and output statistics
|
|
3439
|
+
from there as wanted.
|
|
3440
|
+
* If lowmem=False, use the fast functions to do the 4pcf multipole computation and do
|
|
3441
|
+
the potential conversions lateron.
|
|
3442
|
+
* Default lowmem to None and
|
|
3443
|
+
* - Set to true if any aperture statistics is in stats or we will run into mem error
|
|
3444
|
+
* - Set to false otherwise
|
|
3445
|
+
* - Raise error if lowmen=False and we will have more that 2^31-1 elements at any stage of the computation
|
|
3446
|
+
|
|
3447
|
+
custom_thetacombis: array of inds which theta combis will be selected
|
|
3448
|
+
"""
|
|
3449
|
+
|
|
3450
|
+
## Preparations ##
|
|
3451
|
+
# Build list of statistics to be calculated
|
|
3452
|
+
statistics_avail_4pcf = ["4pcf_real", "4pcf_multipole"]
|
|
3453
|
+
statistics_avail_nap4 = ["N4", "Nap4", "N4c", "Nap4c"]
|
|
3454
|
+
statistics_avail_comp = ["allNap", "all4pcf", "all"]
|
|
3455
|
+
statistics_avail_phys = statistics_avail_4pcf + statistics_avail_nap4
|
|
3456
|
+
statistics_avail = statistics_avail_4pcf + statistics_avail_nap4 + statistics_avail_comp
|
|
3457
|
+
_statistics = []
|
|
3458
|
+
hasintegratedstats = False
|
|
3459
|
+
_strbadstats = lambda stat: ("The statistics `%s` has not been implemented yet. "%stat +
|
|
3460
|
+
"Currently supported statistics are:\n" + str(statistics_avail))
|
|
3461
|
+
if type(statistics) not in [list, str]:
|
|
3462
|
+
raise ValueError("The parameter `statistics` should either be a list or a string.")
|
|
3463
|
+
if type(statistics) is str:
|
|
3464
|
+
if statistics not in statistics_avail:
|
|
3465
|
+
raise ValueError(_strbadstats)
|
|
3466
|
+
statistics = [statistics]
|
|
3467
|
+
if type(statistics) is list:
|
|
3468
|
+
if "all" in statistics:
|
|
3469
|
+
_statistics = statistics_avail_phys
|
|
3470
|
+
elif "all4pcf" in statistics:
|
|
3471
|
+
_statistics.append(statistics_avail_4pcf)
|
|
3472
|
+
elif "allNap" in statistics:
|
|
3473
|
+
_statistics.append(statistics_avail_nap4)
|
|
3474
|
+
_statistics = flatlist(_statistics)
|
|
3475
|
+
for stat in statistics:
|
|
3476
|
+
if stat not in statistics_avail:
|
|
3477
|
+
raise ValueError(_strbadstats)
|
|
3478
|
+
if stat in statistics_avail_phys and stat not in _statistics:
|
|
3479
|
+
_statistics.append(stat)
|
|
3480
|
+
statistics = list(set(flatlist(_statistics)))
|
|
3481
|
+
for stat in statistics:
|
|
3482
|
+
if stat in statistics_avail_nap4:
|
|
3483
|
+
hasintegratedstats = True
|
|
3484
|
+
|
|
3485
|
+
# Check if the output will fit in memory
|
|
3486
|
+
if "4pcf_multipole" in statistics:
|
|
3487
|
+
_nvals = self.nzcombis*(2*self.nmaxs[0]+1)*(2*self.nmaxs[1]+1)*self.nbinsr**3
|
|
3488
|
+
if _nvals>cutlen:
|
|
3489
|
+
raise ValueError(("4pcf in multipole basis will cause memory overflow " +
|
|
3490
|
+
"(requiring %.2fx10^9 > %.2fx10^9 elements)\n"%(_nvals/1e9, cutlen/1e9) +
|
|
3491
|
+
"If you are solely interested in integrated statistics (like Map4), you" +
|
|
3492
|
+
"only need to add those to the `statistics` argument."))
|
|
3493
|
+
if "4pcf_real" in statistics:
|
|
3494
|
+
_nvals = self.nzcombis*self.nbinsphi[0]*self.nbinsphi[1]*self.nbinsr**3
|
|
3495
|
+
if _nvals>cutlen:
|
|
3496
|
+
raise ValueError(("4pcf in real basis will cause memory overflow " +
|
|
3497
|
+
"(requiring %.2fx10^9 > %.2fx10^9 elements)\n"%(_nvals/1e9, cutlen/1e9) +
|
|
3498
|
+
"If you are solely interested in integrated statistics (like Map4), you" +
|
|
3499
|
+
"only need to add those to the `statistics` argument."))
|
|
3500
|
+
|
|
3501
|
+
# Decide on whether to use low-mem functions or not
|
|
3502
|
+
if hasintegratedstats:
|
|
3503
|
+
if lowmem in [False, None]:
|
|
3504
|
+
if not lowmem:
|
|
3505
|
+
print("Low-memory computation enforced for integrated measures of the 4pcf. " +
|
|
3506
|
+
"Set `lowmem` from `%s` to `True`"%str(lowmem))
|
|
3507
|
+
lowmem = True
|
|
3508
|
+
else:
|
|
3509
|
+
if lowmem in [None, False]:
|
|
3510
|
+
maxlen = 0
|
|
3511
|
+
_lowmem = False
|
|
3512
|
+
if "4pcf_multipole" in statistics:
|
|
3513
|
+
_nvals = self.nzcombis*(2*self.nmaxs[0]+1)*(2*self.nmaxs[1]+1)*self.nbinsr**3
|
|
3514
|
+
if _nvals > cutlen:
|
|
3515
|
+
if not lowmem:
|
|
3516
|
+
print("Switching to low-memory computation of 4pcf in multipole basis.")
|
|
3517
|
+
lowmem = True
|
|
3518
|
+
else:
|
|
3519
|
+
lowmem = False
|
|
3520
|
+
if "4pcf_real" in statistics:
|
|
3521
|
+
nvals = self.nzcombis*self.nbinsphi[0]*self.nbinsphi[1]*self.nbinsr**3
|
|
3522
|
+
if _nvals > cutlen:
|
|
3523
|
+
if not lowmem:
|
|
3524
|
+
print("Switching to low-memory computation of 4pcf in real basis.")
|
|
3525
|
+
lowmem = True
|
|
3526
|
+
else:
|
|
3527
|
+
lowmem = False
|
|
3528
|
+
|
|
3529
|
+
# Misc checks
|
|
3530
|
+
self._checkcats(cat, self.spins)
|
|
3531
|
+
|
|
3532
|
+
## Build args for wrapped functions ##
|
|
3533
|
+
# Shortcuts
|
|
3534
|
+
_nmax = self.nmaxs[0]
|
|
3535
|
+
_nnvals = (2*_nmax+1)*(2*_nmax+1)
|
|
3536
|
+
_nbinsr3 = self.nbinsr*self.nbinsr*self.nbinsr
|
|
3537
|
+
_nphis = len(self.phis[0])
|
|
3538
|
+
sc = (2*_nmax+1,2*_nmax+1,self.nzcombis,self.nbinsr,self.nbinsr,self.nbinsr)
|
|
3539
|
+
szr = (self.nbinsz, self.nbinsr)
|
|
3540
|
+
s4pcf = (self.nzcombis,self.nbinsr,self.nbinsr,self.nbinsr,_nphis,_nphis)
|
|
3541
|
+
# Init default args
|
|
3542
|
+
bin_centers = np.zeros(self.nbinsz*self.nbinsr).astype(np.float64)
|
|
3543
|
+
if not cat.hasspatialhash:
|
|
3544
|
+
cat.build_spatialhash(dpix=max(1.,self.max_sep//10.))
|
|
3545
|
+
nregions = np.int32(len(np.argwhere(cat.index_matcher>-1).flatten()))
|
|
3546
|
+
args_basecat = (cat.isinner.astype(np.float64), cat.weight, cat.pos1, cat.pos2,
|
|
3547
|
+
np.int32(cat.ngal), )
|
|
3548
|
+
args_hash = (cat.index_matcher, cat.pixs_galind_bounds, cat.pix_gals, nregions,
|
|
3549
|
+
np.float64(cat.pix1_start), np.float64(cat.pix1_d), np.int32(cat.pix1_n),
|
|
3550
|
+
np.float64(cat.pix2_start), np.float64(cat.pix2_d), np.int32(cat.pix2_n), )
|
|
3551
|
+
|
|
3552
|
+
# Init optional args
|
|
3553
|
+
__lenflag = 10
|
|
3554
|
+
__fillflag = -1
|
|
3555
|
+
if "4pcf_multipole" in statistics:
|
|
3556
|
+
N_n = np.zeros(_nnvals*self.nzcombis*_nbinsr3).astype(np.complex128)
|
|
3557
|
+
alloc_4pcfmultipoles = 1
|
|
3558
|
+
else:
|
|
3559
|
+
N_n = __fillflag*np.zeros(__lenflag).astype(np.complex128)
|
|
3560
|
+
alloc_4pcfmultipoles = 0
|
|
3561
|
+
if "4pcf_real" in statistics:
|
|
3562
|
+
fourpcf = np.zeros(_nphis*_nphis*self.nzcombis*_nbinsr3).astype(np.complex128)
|
|
3563
|
+
alloc_4pcfreal = 1
|
|
3564
|
+
else:
|
|
3565
|
+
fourpcf = __fillflag*np.ones(__lenflag).astype(np.complex128)
|
|
3566
|
+
alloc_4pcfreal = 0
|
|
3567
|
+
if hasintegratedstats:
|
|
3568
|
+
if mapradii is None:
|
|
3569
|
+
raise ValueError("Aperture radii need to be specified in variable `mapradii`.")
|
|
3570
|
+
mapradii = mapradii.astype(np.float64)
|
|
3571
|
+
N4correlators = np.zeros(self.nzcombis*len(mapradii)).astype(np.complex128)
|
|
3572
|
+
else:
|
|
3573
|
+
mapradii = __fillflag*np.ones(__lenflag).astype(np.float64)
|
|
3574
|
+
N4correlators = __fillflag*np.ones(__lenflag).astype(np.complex128)
|
|
3575
|
+
|
|
3576
|
+
|
|
3577
|
+
# Build args based on chosen methods
|
|
3578
|
+
if self.method=="Discrete" and not lowmem:
|
|
3579
|
+
raise NotImplementedError
|
|
3580
|
+
if self.method=="Discrete" and lowmem:
|
|
3581
|
+
raise NotImplementedError
|
|
3582
|
+
if self.method=="Tree" and lowmem:
|
|
3583
|
+
# Prepare mask for nonredundant theta- and multipole configurations
|
|
3584
|
+
_resradial = gen_thetacombis_fourthorder(nbinsr=self.nbinsr, nthreads=self.nthreads, batchsize=batchsize,
|
|
3585
|
+
batchsize_max=self.thetabatchsize_max, ordered=True, custom=custom_thetacombis,
|
|
3586
|
+
verbose=self._verbose_python)
|
|
3587
|
+
_, _, thetacombis_batches, cumnthetacombis_batches, nthetacombis_batches, nbatches = _resradial
|
|
3588
|
+
assert(self.nmaxs[0]==self.nmaxs[1])
|
|
3589
|
+
_resmultipoles = gen_n2n3indices_Upsfourth(self.nmaxs[0])
|
|
3590
|
+
_shape, _inds, _n2s, _n3s = _resmultipoles
|
|
3591
|
+
|
|
3592
|
+
# Prepare reduced catalogs
|
|
3593
|
+
cutfirst = np.int32(self.tree_resos[0]==0.)
|
|
3594
|
+
mhash = cat.multihash(dpixs=self.tree_resos[cutfirst:], dpix_hash=self.tree_resos[-1],
|
|
3595
|
+
shuffle=self.shuffle_pix, normed=False)
|
|
3596
|
+
(ngal_resos, pos1s, pos2s, weights, zbins, isinners, allfields,
|
|
3597
|
+
index_matchers, pixs_galind_bounds, pix_gals, dpixs1_true, dpixs2_true) = mhash
|
|
3598
|
+
weight_resos = np.concatenate(weights).astype(np.float64)
|
|
3599
|
+
pos1_resos = np.concatenate(pos1s).astype(np.float64)
|
|
3600
|
+
pos2_resos = np.concatenate(pos2s).astype(np.float64)
|
|
3601
|
+
zbin_resos = np.concatenate(zbins).astype(np.int32)
|
|
3602
|
+
isinner_resos = np.concatenate(isinners).astype(np.float64)
|
|
3603
|
+
index_matcher_resos = np.concatenate(index_matchers).astype(np.int32)
|
|
3604
|
+
pixs_galind_bounds_resos = np.concatenate(pixs_galind_bounds).astype(np.int32)
|
|
3605
|
+
pix_gals_resos = np.concatenate(pix_gals).astype(np.int32)
|
|
3606
|
+
index_matcher_flat = np.argwhere(cat.index_matcher>-1).flatten()
|
|
3607
|
+
nregions = len(index_matcher_flat)
|
|
3608
|
+
# Build args
|
|
3609
|
+
args_basesetup = (np.int32(_nmax),
|
|
3610
|
+
np.float64(self.min_sep), np.float64(self.max_sep), np.int32(self.nbinsr),
|
|
3611
|
+
np.int32(self.multicountcorr),
|
|
3612
|
+
_inds, np.int32(len(_inds)), self.phis[0].astype(np.float64),
|
|
3613
|
+
2*np.pi/_nphis*np.ones(_nphis, dtype=np.float64), np.int32(_nphis), )
|
|
3614
|
+
args_resos = (np.int32(self.tree_nresos), self.tree_redges, np.array(ngal_resos, dtype=np.int32),
|
|
3615
|
+
isinner_resos, weight_resos, pos1_resos, pos2_resos,
|
|
3616
|
+
index_matcher_resos, pixs_galind_bounds_resos, pix_gals_resos, np.int32(nregions), )
|
|
3617
|
+
args_hash = (np.float64(cat.pix1_start), np.float64(cat.pix1_d), np.int32(cat.pix1_n),
|
|
3618
|
+
np.float64(cat.pix2_start), np.float64(cat.pix2_d), np.int32(cat.pix2_n), )
|
|
3619
|
+
args_thetas = (thetacombis_batches, nthetacombis_batches, cumnthetacombis_batches, nbatches, )
|
|
3620
|
+
args_nap4 = (mapradii, np.int32(len(mapradii)), N4correlators)
|
|
3621
|
+
args_4pcf = (np.int32(alloc_4pcfmultipoles), np.int32(alloc_4pcfreal),
|
|
3622
|
+
bin_centers, N_n, fourpcf)
|
|
3623
|
+
args = (*args_basecat,
|
|
3624
|
+
*args_basesetup,
|
|
3625
|
+
*args_resos,
|
|
3626
|
+
*args_hash,
|
|
3627
|
+
*args_thetas,
|
|
3628
|
+
np.int32(self.nthreads),
|
|
3629
|
+
*args_nap4,
|
|
3630
|
+
*args_4pcf)
|
|
3631
|
+
func = self.clib.alloc_notomoNap4_tree_nnnn
|
|
3632
|
+
|
|
3633
|
+
# Optionally print the arguments
|
|
3634
|
+
if self._verbose_debug:
|
|
3635
|
+
print("We pass the following arguments:")
|
|
3636
|
+
for elarg, arg in enumerate(args):
|
|
3637
|
+
toprint = (elarg, type(arg),)
|
|
3638
|
+
if isinstance(arg, np.ndarray):
|
|
3639
|
+
toprint += (type(arg[0]), arg.shape)
|
|
3640
|
+
try:
|
|
3641
|
+
toprint += (func.argtypes[elarg], )
|
|
3642
|
+
print(toprint)
|
|
3643
|
+
print(arg)
|
|
3644
|
+
except:
|
|
3645
|
+
print("We did have a problem for arg %i"%elarg)
|
|
3646
|
+
|
|
3647
|
+
## Compute 4th order stats ##
|
|
3648
|
+
func(*args)
|
|
3649
|
+
|
|
3650
|
+
## Massage the output ##
|
|
3651
|
+
istatout = ()
|
|
3652
|
+
self.bin_centers = bin_centers.reshape(szr)
|
|
3653
|
+
self.bin_centers_mean = np.mean(self.bin_centers, axis=0)
|
|
3654
|
+
if "4pcf_multipole" in statistics:
|
|
3655
|
+
self.npcf_multipoles = N_n.reshape(sc)
|
|
3656
|
+
if "4pcf_real" in statistics:
|
|
3657
|
+
if lowmem:
|
|
3658
|
+
self.npcf = fourpcf.reshape(s4pcf)
|
|
3659
|
+
else:
|
|
3660
|
+
if self._verbose_python:
|
|
3661
|
+
print("Transforming output to real space basis")
|
|
3662
|
+
self.multipoles2npcf_c()
|
|
3663
|
+
if hasintegratedstats:
|
|
3664
|
+
if "N4" in statistics:
|
|
3665
|
+
istatout += (N4correlators.reshape((self.nzcombis,len(mapradii))), )
|
|
3666
|
+
# TODO allocate map4, map4c etc.
|
|
3667
|
+
|
|
3668
|
+
return istatout
|
|
3669
|
+
|
|
3670
|
+
def multipoles2npcf_singlethetcombi(self, elthet1, elthet2, elthet3):
|
|
3671
|
+
r""" Converts a 4PCF in the multipole basis in the real space basis for a fixed combination of radial bins.
|
|
3672
|
+
|
|
3673
|
+
Returns:
|
|
3674
|
+
--------
|
|
3675
|
+
npcf_out: np.ndarray
|
|
3676
|
+
Natural 4PCF components in the real-space basis for all angular combinations.
|
|
3677
|
+
npcf_norm_out: np.ndarray
|
|
3678
|
+
4PCF weighted counts in the real-space basis for all angular combinations.
|
|
3679
|
+
"""
|
|
3680
|
+
|
|
3681
|
+
_phis1 = self.phis[0].astype(np.float64)
|
|
3682
|
+
_phis2 = self.phis[1].astype(np.float64)
|
|
3683
|
+
_nphis1 = len(self.phis[0])
|
|
3684
|
+
_nphis2 = len(self.phis[1])
|
|
3685
|
+
nnvals, _, nzcombis, nbinsr, _, _ = np.shape(self.npcf_multipoles)
|
|
3686
|
+
|
|
3687
|
+
N_in = self.npcf_multipoles[...,elthet1,elthet2,elthet3].flatten()
|
|
3688
|
+
npcf_out = np.zeros(nzcombis*_nphis1*_nphis2, dtype=np.complex128)
|
|
3689
|
+
|
|
3690
|
+
self.clib.multipoles2npcf_nnnn_singletheta(
|
|
3691
|
+
N_in, self.nmaxs[0], self.nmaxs[1],
|
|
3692
|
+
self.bin_centers_mean[elthet1], self.bin_centers_mean[elthet2], self.bin_centers_mean[elthet3],
|
|
3693
|
+
_phis1, _phis2, _nphis1, _nphis2,
|
|
3694
|
+
npcf_out)
|
|
3695
|
+
|
|
3696
|
+
return npcf_out.reshape(( _nphis1,_nphis2))
|