orpheus-npcf 0.2.1__cp310-cp310-musllinux_1_2_x86_64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
orpheus/npcf_fourth.py ADDED
@@ -0,0 +1,1716 @@
1
+ import numpy as np
2
+ import ctypes as ct
3
+ from functools import reduce
4
+ import operator
5
+ from scipy.interpolate import interp1d
6
+
7
+ from .utils import flatlist, gen_thetacombis_fourthorder, gen_n2n3indices_Upsfourth
8
+ from .npcf_base import BinnedNPCF
9
+ from .npcf_second import GGCorrelation
10
+
11
+ __all__ = ["NNNNCorrelation_NoTomo", "GGGGCorrelation_NoTomo"]
12
+
13
+ class NNNNCorrelation_NoTomo(BinnedNPCF):
14
+ r""" Class containing methods to measure and and obtain statistics that are built
15
+ from nontomographic fourth-order scalar correlation functions.
16
+
17
+ Attributes
18
+ ----------
19
+ min_sep: float
20
+ The smallest distance of each vertex for which the NPCF is computed.
21
+ max_sep: float
22
+ The largest distance of each vertex for which the NPCF is computed.
23
+ thetabatchsize_max: int, optional
24
+ The largest number of radial bin combinations that are processed in parallel.
25
+ Defaults to ``10 000``.
26
+
27
+ Notes
28
+ -----
29
+ Inherits all other parameters and attributes from :class:`BinnedNPCF`.
30
+ Additional child-specific parameters can be passed via ``kwargs``.
31
+ Either ``nbinsr`` or ``binsize`` has to be provided to fix the binning scheme .
32
+
33
+ """
34
+
35
+ def __init__(self, min_sep, max_sep, verbose=False, thetabatchsize_max=10000, method="Tree", **kwargs):
36
+ super().__init__(order=4, spins=np.array([0,0,0,0], dtype=np.int32),
37
+ n_cfs=1, min_sep=min_sep, max_sep=max_sep,
38
+ method=method, methods_avail=["Tree"], **kwargs)
39
+
40
+ self.thetabatchsize_max = thetabatchsize_max
41
+ self.nbinsz = 1
42
+ self.nzcombis = 1
43
+
44
+ def process(self, cat, statistics="all", tofile=False, apply_edge_correction=False,
45
+ lowmem=True, mapradii=None, batchsize=None, custom_thetacombis=None, cutlen=2**31-1):
46
+ r"""
47
+ Arguments:
48
+
49
+ Logic works as follows:
50
+ * Keyword 'statistics' \in [4pcf_real, 4pcf_multipoles, N4, Nap4, Nap4, Nap4c, allNap, all4pcf, all]
51
+ * - If 4pcf_multipoles in statistics --> save 4pcf_multipoles
52
+ * - If 4pcf_real in statistics --> save 4pcf_real
53
+ * - If only N4 in statistics --> Do not save any 4pcf. This is really the lowmem case.
54
+ * - allNap, all4pcf, all are abbreviations as expected
55
+ * If lowmem=True, uses the inefficient, but lowmem function for computation and output statistics
56
+ from there as wanted.
57
+ * If lowmem=False, use the fast functions to do the 4pcf multipole computation and do
58
+ the potential conversions lateron.
59
+ * Default lowmem to None and
60
+ * - Set to true if any aperture statistics is in stats or we will run into mem error
61
+ * - Set to false otherwise
62
+ * - Raise error if lowmen=False and we will have more that 2^31-1 elements at any stage of the computation
63
+
64
+ custom_thetacombis: array of inds which theta combis will be selected
65
+ """
66
+
67
+ ## Preparations ##
68
+ # Build list of statistics to be calculated
69
+ statistics_avail_4pcf = ["4pcf_real", "4pcf_multipole"]
70
+ statistics_avail_nap4 = ["N4", "Nap4", "N4c", "Nap4c"]
71
+ statistics_avail_comp = ["allNap", "all4pcf", "all"]
72
+ statistics_avail_phys = statistics_avail_4pcf + statistics_avail_nap4
73
+ statistics_avail = statistics_avail_4pcf + statistics_avail_nap4 + statistics_avail_comp
74
+ _statistics = []
75
+ hasintegratedstats = False
76
+ _strbadstats = lambda stat: ("The statistics `%s` has not been implemented yet. "%stat +
77
+ "Currently supported statistics are:\n" + str(statistics_avail))
78
+ if type(statistics) not in [list, str]:
79
+ raise ValueError("The parameter `statistics` should either be a list or a string.")
80
+ if type(statistics) is str:
81
+ if statistics not in statistics_avail:
82
+ raise ValueError(_strbadstats)
83
+ statistics = [statistics]
84
+ if type(statistics) is list:
85
+ if "all" in statistics:
86
+ _statistics = statistics_avail_phys
87
+ elif "all4pcf" in statistics:
88
+ _statistics.append(statistics_avail_4pcf)
89
+ elif "allNap" in statistics:
90
+ _statistics.append(statistics_avail_nap4)
91
+ _statistics = flatlist(_statistics)
92
+ for stat in statistics:
93
+ if stat not in statistics_avail:
94
+ raise ValueError(_strbadstats)
95
+ if stat in statistics_avail_phys and stat not in _statistics:
96
+ _statistics.append(stat)
97
+ statistics = list(set(flatlist(_statistics)))
98
+ for stat in statistics:
99
+ if stat in statistics_avail_nap4:
100
+ hasintegratedstats = True
101
+
102
+ # Check if the output will fit in memory
103
+ if "4pcf_multipole" in statistics:
104
+ _nvals = self.nzcombis*(2*self.nmaxs[0]+1)*(2*self.nmaxs[1]+1)*self.nbinsr**3
105
+ if _nvals>cutlen:
106
+ raise ValueError(("4pcf in multipole basis will cause memory overflow " +
107
+ "(requiring %.2fx10^9 > %.2fx10^9 elements)\n"%(_nvals/1e9, cutlen/1e9) +
108
+ "If you are solely interested in integrated statistics (like Map4), you" +
109
+ "only need to add those to the `statistics` argument."))
110
+ if "4pcf_real" in statistics:
111
+ _nvals = self.nzcombis*self.nbinsphi[0]*self.nbinsphi[1]*self.nbinsr**3
112
+ if _nvals>cutlen:
113
+ raise ValueError(("4pcf in real basis will cause memory overflow " +
114
+ "(requiring %.2fx10^9 > %.2fx10^9 elements)\n"%(_nvals/1e9, cutlen/1e9) +
115
+ "If you are solely interested in integrated statistics (like Map4), you" +
116
+ "only need to add those to the `statistics` argument."))
117
+
118
+ # Decide on whether to use low-mem functions or not
119
+ if hasintegratedstats:
120
+ if lowmem in [False, None]:
121
+ if not lowmem:
122
+ print("Low-memory computation enforced for integrated measures of the 4pcf. " +
123
+ "Set `lowmem` from `%s` to `True`"%str(lowmem))
124
+ lowmem = True
125
+ else:
126
+ if lowmem in [None, False]:
127
+ maxlen = 0
128
+ _lowmem = False
129
+ if "4pcf_multipole" in statistics:
130
+ _nvals = self.nzcombis*(2*self.nmaxs[0]+1)*(2*self.nmaxs[1]+1)*self.nbinsr**3
131
+ if _nvals > cutlen:
132
+ if not lowmem:
133
+ print("Switching to low-memory computation of 4pcf in multipole basis.")
134
+ lowmem = True
135
+ else:
136
+ lowmem = False
137
+ if "4pcf_real" in statistics:
138
+ nvals = self.nzcombis*self.nbinsphi[0]*self.nbinsphi[1]*self.nbinsr**3
139
+ if _nvals > cutlen:
140
+ if not lowmem:
141
+ print("Switching to low-memory computation of 4pcf in real basis.")
142
+ lowmem = True
143
+ else:
144
+ lowmem = False
145
+
146
+ # Misc checks
147
+ self._checkcats(cat, self.spins)
148
+
149
+ ## Build args for wrapped functions ##
150
+ # Shortcuts
151
+ _nmax = self.nmaxs[0]
152
+ _nnvals = (2*_nmax+1)*(2*_nmax+1)
153
+ _nbinsr3 = self.nbinsr*self.nbinsr*self.nbinsr
154
+ _nphis = len(self.phis[0])
155
+ sc = (2*_nmax+1,2*_nmax+1,self.nzcombis,self.nbinsr,self.nbinsr,self.nbinsr)
156
+ szr = (self.nbinsz, self.nbinsr)
157
+ s4pcf = (self.nzcombis,self.nbinsr,self.nbinsr,self.nbinsr,_nphis,_nphis)
158
+ # Init default args
159
+ bin_centers = np.zeros(self.nbinsz*self.nbinsr).astype(np.float64)
160
+ if not cat.hasspatialhash:
161
+ cat.build_spatialhash(dpix=max(1.,self.max_sep//10.))
162
+ nregions = np.int32(len(np.argwhere(cat.index_matcher>-1).flatten()))
163
+ args_basecat = (cat.isinner.astype(np.float64), cat.weight, cat.pos1, cat.pos2,
164
+ np.int32(cat.ngal), )
165
+ args_hash = (cat.index_matcher, cat.pixs_galind_bounds, cat.pix_gals, nregions,
166
+ np.float64(cat.pix1_start), np.float64(cat.pix1_d), np.int32(cat.pix1_n),
167
+ np.float64(cat.pix2_start), np.float64(cat.pix2_d), np.int32(cat.pix2_n), )
168
+
169
+ # Init optional args
170
+ __lenflag = 10
171
+ __fillflag = -1
172
+ if "4pcf_multipole" in statistics:
173
+ N_n = np.zeros(_nnvals*self.nzcombis*_nbinsr3).astype(np.complex128)
174
+ alloc_4pcfmultipoles = 1
175
+ else:
176
+ N_n = __fillflag*np.zeros(__lenflag).astype(np.complex128)
177
+ alloc_4pcfmultipoles = 0
178
+ if "4pcf_real" in statistics:
179
+ fourpcf = np.zeros(_nphis*_nphis*self.nzcombis*_nbinsr3).astype(np.complex128)
180
+ alloc_4pcfreal = 1
181
+ else:
182
+ fourpcf = __fillflag*np.ones(__lenflag).astype(np.complex128)
183
+ alloc_4pcfreal = 0
184
+ if hasintegratedstats:
185
+ if mapradii is None:
186
+ raise ValueError("Aperture radii need to be specified in variable `mapradii`.")
187
+ mapradii = mapradii.astype(np.float64)
188
+ N4correlators = np.zeros(self.nzcombis*len(mapradii)).astype(np.complex128)
189
+ else:
190
+ mapradii = __fillflag*np.ones(__lenflag).astype(np.float64)
191
+ N4correlators = __fillflag*np.ones(__lenflag).astype(np.complex128)
192
+
193
+
194
+ # Build args based on chosen methods
195
+ if self.method=="Discrete" and not lowmem:
196
+ raise NotImplementedError
197
+ if self.method=="Discrete" and lowmem:
198
+ raise NotImplementedError
199
+ if self.method=="Tree" and lowmem:
200
+ # Prepare mask for nonredundant theta- and multipole configurations
201
+ _resradial = gen_thetacombis_fourthorder(nbinsr=self.nbinsr, nthreads=self.nthreads, batchsize=batchsize,
202
+ batchsize_max=self.thetabatchsize_max, ordered=True, custom=custom_thetacombis,
203
+ verbose=self._verbose_python)
204
+ _, _, thetacombis_batches, cumnthetacombis_batches, nthetacombis_batches, nbatches = _resradial
205
+ assert(self.nmaxs[0]==self.nmaxs[1])
206
+ _resmultipoles = gen_n2n3indices_Upsfourth(self.nmaxs[0])
207
+ _shape, _inds, _n2s, _n3s = _resmultipoles
208
+
209
+ # Prepare reduced catalogs
210
+ cutfirst = np.int32(self.tree_resos[0]==0.)
211
+ mhash = cat.multihash(dpixs=self.tree_resos[cutfirst:], dpix_hash=self.tree_resos[-1],
212
+ shuffle=self.shuffle_pix, normed=False)
213
+ (ngal_resos, pos1s, pos2s, weights, zbins, isinners, allfields,
214
+ index_matchers, pixs_galind_bounds, pix_gals, dpixs1_true, dpixs2_true) = mhash
215
+ weight_resos = np.concatenate(weights).astype(np.float64)
216
+ pos1_resos = np.concatenate(pos1s).astype(np.float64)
217
+ pos2_resos = np.concatenate(pos2s).astype(np.float64)
218
+ zbin_resos = np.concatenate(zbins).astype(np.int32)
219
+ isinner_resos = np.concatenate(isinners).astype(np.float64)
220
+ index_matcher_resos = np.concatenate(index_matchers).astype(np.int32)
221
+ pixs_galind_bounds_resos = np.concatenate(pixs_galind_bounds).astype(np.int32)
222
+ pix_gals_resos = np.concatenate(pix_gals).astype(np.int32)
223
+ index_matcher_flat = np.argwhere(cat.index_matcher>-1).flatten()
224
+ nregions = len(index_matcher_flat)
225
+ # Build args
226
+ args_basesetup = (np.int32(_nmax),
227
+ np.float64(self.min_sep), np.float64(self.max_sep), np.int32(self.nbinsr),
228
+ np.int32(self.multicountcorr),
229
+ _inds, np.int32(len(_inds)), self.phis[0].astype(np.float64),
230
+ 2*np.pi/_nphis*np.ones(_nphis, dtype=np.float64), np.int32(_nphis), )
231
+ args_resos = (np.int32(self.tree_nresos), self.tree_redges, np.array(ngal_resos, dtype=np.int32),
232
+ isinner_resos, weight_resos, pos1_resos, pos2_resos,
233
+ index_matcher_resos, pixs_galind_bounds_resos, pix_gals_resos, np.int32(nregions), )
234
+ args_hash = (np.float64(cat.pix1_start), np.float64(cat.pix1_d), np.int32(cat.pix1_n),
235
+ np.float64(cat.pix2_start), np.float64(cat.pix2_d), np.int32(cat.pix2_n), )
236
+ args_thetas = (thetacombis_batches, nthetacombis_batches, cumnthetacombis_batches, nbatches, )
237
+ args_nap4 = (mapradii, np.int32(len(mapradii)), N4correlators)
238
+ args_4pcf = (np.int32(alloc_4pcfmultipoles), np.int32(alloc_4pcfreal),
239
+ bin_centers, N_n, fourpcf)
240
+ args = (*args_basecat,
241
+ *args_basesetup,
242
+ *args_resos,
243
+ *args_hash,
244
+ *args_thetas,
245
+ np.int32(self.nthreads),
246
+ *args_nap4,
247
+ *args_4pcf)
248
+ func = self.clib.alloc_notomoNap4_tree_nnnn
249
+
250
+ # Optionally print the arguments
251
+ if self._verbose_debug:
252
+ print("We pass the following arguments:")
253
+ for elarg, arg in enumerate(args):
254
+ toprint = (elarg, type(arg),)
255
+ if isinstance(arg, np.ndarray):
256
+ toprint += (type(arg[0]), arg.shape)
257
+ try:
258
+ toprint += (func.argtypes[elarg], )
259
+ print(toprint)
260
+ print(arg)
261
+ except:
262
+ print("We did have a problem for arg %i"%elarg)
263
+
264
+ ## Compute 4th order stats ##
265
+ func(*args)
266
+
267
+ ## Massage the output ##
268
+ istatout = ()
269
+ self.bin_centers = bin_centers.reshape(szr)
270
+ self.bin_centers_mean = np.mean(self.bin_centers, axis=0)
271
+ if "4pcf_multipole" in statistics:
272
+ self.npcf_multipoles = N_n.reshape(sc)
273
+ if "4pcf_real" in statistics:
274
+ if lowmem:
275
+ self.npcf = fourpcf.reshape(s4pcf)
276
+ else:
277
+ if self._verbose_python:
278
+ print("Transforming output to real space basis")
279
+ self.multipoles2npcf_c()
280
+ if hasintegratedstats:
281
+ if "N4" in statistics:
282
+ istatout += (N4correlators.reshape((self.nzcombis,len(mapradii))), )
283
+ # TODO allocate map4, map4c etc.
284
+
285
+ return istatout
286
+
287
+ def multipoles2npcf_singlethetcombi(self, elthet1, elthet2, elthet3):
288
+ r""" Converts a 4PCF in the multipole basis in the real space basis for a fixed combination of radial bins.
289
+
290
+ Returns:
291
+ --------
292
+ npcf_out: np.ndarray
293
+ Natural 4PCF components in the real-space basis for all angular combinations.
294
+ npcf_norm_out: np.ndarray
295
+ 4PCF weighted counts in the real-space basis for all angular combinations.
296
+ """
297
+
298
+ _phis1 = self.phis[0].astype(np.float64)
299
+ _phis2 = self.phis[1].astype(np.float64)
300
+ _nphis1 = len(self.phis[0])
301
+ _nphis2 = len(self.phis[1])
302
+ nnvals, _, nzcombis, nbinsr, _, _ = np.shape(self.npcf_multipoles)
303
+
304
+ N_in = self.npcf_multipoles[...,elthet1,elthet2,elthet3].flatten()
305
+ npcf_out = np.zeros(nzcombis*_nphis1*_nphis2, dtype=np.complex128)
306
+
307
+ self.clib.multipoles2npcf_nnnn_singletheta(
308
+ N_in, self.nmaxs[0], self.nmaxs[1],
309
+ self.bin_centers_mean[elthet1], self.bin_centers_mean[elthet2], self.bin_centers_mean[elthet3],
310
+ _phis1, _phis2, _nphis1, _nphis2,
311
+ npcf_out)
312
+
313
+ return npcf_out.reshape(( _nphis1,_nphis2))
314
+
315
+
316
+ class GGGGCorrelation_NoTomo(BinnedNPCF):
317
+ r""" Class containing methods to measure and and obtain statistics that are built
318
+ from nontomographic fourth-order shear correlation functions.
319
+
320
+ Attributes
321
+ ----------
322
+ min_sep: float
323
+ The smallest distance of each vertex for which the NPCF is computed.
324
+ max_sep: float
325
+ The largest distance of each vertex for which the NPCF is computed.
326
+ thetabatchsize_max: int, optional
327
+ The largest number of radial bin combinations that are processed in parallel.
328
+ Defaults to ``10 000``.
329
+
330
+ Notes
331
+ -----
332
+ Inherits all other parameters and attributes from :class:`BinnedNPCF`.
333
+ Additional child-specific parameters can be passed via ``kwargs``.
334
+ Either ``nbinsr`` or ``binsize`` has to be provided to fix the binning scheme .
335
+
336
+ """
337
+
338
+ def __init__(self, min_sep, max_sep, thetabatchsize_max=10000, method="Tree", **kwargs):
339
+
340
+ super().__init__(order=4, spins=np.array([2,2,2,2], dtype=np.int32),
341
+ n_cfs=8, min_sep=min_sep, max_sep=max_sep,
342
+ method=method, methods_avail=["Discrete", "Tree"], **kwargs)
343
+
344
+ self.thetabatchsize_max = thetabatchsize_max
345
+ self.projection = None
346
+ self.projections_avail = [None, "X", "Centroid"]
347
+ self.proj_dict = {"X":0, "Centroid":1}
348
+ self.nbinsz = 1
349
+ self.nzcombis = 1
350
+
351
+ # (Add here any newly implemented projections)
352
+ self._initprojections(self)
353
+ self.project["X"]["Centroid"] = self._x2centroid
354
+
355
+ def process(self, cat, statistics="all", tofile=False, apply_edge_correction=False, projection="X",
356
+ lowmem=None, mapradii=None, batchsize=None, custom_thetacombis=None, cutlen=2**31-1):
357
+ r"""
358
+ Arguments:
359
+
360
+ Logic works as follows:
361
+ * Keyword 'statistics' \in [4pcf_real, 4pcf_multipoles, M4, Map4, M4c, Map4c, allMap, all4pcf, all]
362
+ * - If 4pcf_multipoles in statistics --> save 4pcf_multipoles
363
+ * - If 4pcf_real in statistics --> save 4pcf_real
364
+ * - If only M4 in statistics --> Do not save any 4pcf. This is really the lowmem case.
365
+ * - allMap, all4pcf, all are abbreviations as expected
366
+ * If lowmem=True, uses the inefficient, but lowmem function for computation and output statistics
367
+ from there as wanted.
368
+ * If lowmem=False, use the fast functions to do the 4pcf multipole computation and do
369
+ the potential conversions lateron.
370
+ * Default lowmem to None and
371
+ * - Set to true if any aperture statistics is in stats or we will run into mem error
372
+ * - Set to false otherwise
373
+ * - Raise error if lowmen=False and we will have more that 2^31-1 elements at any stage of the computation
374
+
375
+ custom_thetacombis: array of inds which theta combis will be selected
376
+ """
377
+
378
+ ## Preparations ##
379
+ # Build list of statistics to be calculated
380
+ statistics_avail_4pcf = ["4pcf_real", "4pcf_multipole"]
381
+ statistics_avail_map4 = ["M4", "Map4", "M4c", "Map4c"]
382
+ statistics_avail_comp = ["allMap", "all4pcf", "all"]
383
+ statistics_avail_phys = statistics_avail_4pcf + statistics_avail_map4
384
+ statistics_avail = statistics_avail_4pcf + statistics_avail_map4 + statistics_avail_comp
385
+ _statistics = []
386
+ hasintegratedstats = False
387
+ _strbadstats = lambda stat: ("The statistics `%s` has not been implemented yet. "%stat +
388
+ "Currently supported statistics are:\n" + str(statistics_avail))
389
+ if type(statistics) not in [list, str]:
390
+ raise ValueError("The parameter `statistics` should either be a list or a string.")
391
+ if type(statistics) is str:
392
+ if statistics not in statistics_avail:
393
+ raise ValueError(_strbadstats)
394
+ statistics = [statistics]
395
+ if type(statistics) is list:
396
+ if "all" in statistics:
397
+ _statistics = statistics_avail_phys
398
+ elif "all4pcf" in statistics:
399
+ _statistics.append(statistics_avail_4pcf)
400
+ elif "allMap" in statistics:
401
+ _statistics.append(statistics_avail_map4)
402
+ _statistics = flatlist(_statistics)
403
+ for stat in statistics:
404
+ if stat not in statistics_avail:
405
+ raise ValueError(_strbadstats)
406
+ if stat in statistics_avail_phys and stat not in _statistics:
407
+ _statistics.append(stat)
408
+ statistics = list(set(flatlist(_statistics)))
409
+ for stat in statistics:
410
+ if stat in statistics_avail_map4:
411
+ hasintegratedstats = True
412
+
413
+ # Check if the output will fit in memory
414
+ if "4pcf_multipole" in statistics:
415
+ _nvals = 8*self.nzcombis*(2*self.nmaxs[0]+1)*(2*self.nmaxs[1]+1)*self.nbinsr**3
416
+ if _nvals>cutlen:
417
+ raise ValueError(("4pcf in multipole basis will cause memory overflow " +
418
+ "(requiring %.2fx10^9 > %.2fx10^9 elements)\n"%(_nvals/1e9, cutlen/1e9) +
419
+ "If you are solely interested in integrated statistics (like Map4), you" +
420
+ "only need to add those to the `statistics` argument."))
421
+ if "4pcf_real" in statistics:
422
+ _nvals = 8*self.nzcombis*self.nbinsphi[0]*self.nbinsphi[1]*self.nbinsr**3
423
+ if _nvals>cutlen:
424
+ raise ValueError(("4pcf in real basis will cause memory overflow " +
425
+ "(requiring %.2fx10^9 > %.2fx10^9 elements)\n"%(_nvals/1e9, cutlen/1e9) +
426
+ "If you are solely interested in integrated statistics (like Map4), you" +
427
+ "only need to add those to the `statistics` argument."))
428
+
429
+ # Decide on whether to use low-mem functions or not
430
+ if hasintegratedstats:
431
+ if lowmem in [False, None]:
432
+ if not lowmem:
433
+ print("Low-memory computation enforced for integrated measures of the 4pcf. " +
434
+ "Set `lowmem` from `%s` to `True`"%str(lowmem))
435
+ lowmem = True
436
+ else:
437
+ if lowmem in [None, False]:
438
+ maxlen = 0
439
+ _lowmem = False
440
+ if "4pcf_multipole" in statistics:
441
+ _nvals = 8*self.nzcombis*(2*self.nmaxs[0]+1)*(2*self.nmaxs[1]+1)*self.nbinsr**3
442
+ if _nvals > cutlen:
443
+ if not lowmem:
444
+ print("Switching to low-memory computation of 4pcf in multipole basis.")
445
+ lowmem = True
446
+ else:
447
+ lowmem = False
448
+ if "4pcf_real" in statistics:
449
+ nvals = 8*self.nzcombis*self.nbinsphi[0]*self.nbinsphi[1]*self.nbinsr**3
450
+ if _nvals > cutlen:
451
+ if not lowmem:
452
+ print("Switching to low-memory computation of 4pcf in real basis.")
453
+ lowmem = True
454
+ else:
455
+ lowmem = False
456
+
457
+ # Misc checks
458
+ assert(projection in self.projections_avail)
459
+ self._checkcats(cat, self.spins)
460
+ i_projection = np.int32(self.proj_dict[projection])
461
+
462
+ ## Build args for wrapped functions ##
463
+ # Shortcuts
464
+ _nmax = self.nmaxs[0]
465
+ _nnvals = (2*_nmax+1)*(2*_nmax+1)
466
+ _nbinsr3 = self.nbinsr*self.nbinsr*self.nbinsr
467
+ _nphis = len(self.phis[0])
468
+ sc = (8,2*_nmax+1,2*_nmax+1,self.nzcombis,self.nbinsr,self.nbinsr,self.nbinsr)
469
+ sn = (2*_nmax+1,2*_nmax+1,self.nzcombis,self.nbinsr,self.nbinsr,self.nbinsr)
470
+ szr = (self.nbinsz, self.nbinsr)
471
+ s4pcf = (8,self.nzcombis,self.nbinsr,self.nbinsr,self.nbinsr,_nphis,_nphis)
472
+ s4pcfn = (self.nzcombis,self.nbinsr,self.nbinsr,self.nbinsr,_nphis,_nphis)
473
+ # Init default args
474
+ bin_centers = np.zeros(self.nbinsz*self.nbinsr).astype(np.float64)
475
+ if not cat.hasspatialhash:
476
+ cat.build_spatialhash(dpix=max(1.,self.max_sep//10.))
477
+ nregions = np.int32(len(np.argwhere(cat.index_matcher>-1).flatten()))
478
+ args_basecat = (cat.isinner.astype(np.float64), cat.weight, cat.pos1, cat.pos2,
479
+ cat.tracer_1, cat.tracer_2, np.int32(cat.ngal), )
480
+ args_hash = (cat.index_matcher, cat.pixs_galind_bounds, cat.pix_gals, nregions,
481
+ np.float64(cat.pix1_start), np.float64(cat.pix1_d), np.int32(cat.pix1_n),
482
+ np.float64(cat.pix2_start), np.float64(cat.pix2_d), np.int32(cat.pix2_n), )
483
+
484
+ # Init optional args
485
+ __lenflag = 10
486
+ __fillflag = -1
487
+ if "4pcf_multipole" in statistics:
488
+ Upsilon_n = np.zeros(self.n_cfs*_nnvals*self.nzcombis*_nbinsr3).astype(np.complex128)
489
+ N_n = np.zeros(_nnvals*self.nzcombis*_nbinsr3).astype(np.complex128)
490
+ alloc_4pcfmultipoles = 1
491
+ else:
492
+ Upsilon_n = __fillflag*np.ones(__lenflag).astype(np.complex128)
493
+ N_n = __fillflag*np.zeros(__lenflag).astype(np.complex128)
494
+ alloc_4pcfmultipoles = 0
495
+ if "4pcf_real" in statistics:
496
+ fourpcf = np.zeros(8*_nphis*_nphis*self.nzcombis*_nbinsr3).astype(np.complex128)
497
+ fourpcf_norm = np.zeros(_nphis*_nphis*self.nzcombis*_nbinsr3).astype(np.complex128)
498
+ alloc_4pcfreal = 1
499
+ else:
500
+ fourpcf = __fillflag*np.ones(__lenflag).astype(np.complex128)
501
+ fourpcf_norm = __fillflag*np.ones(__lenflag).astype(np.complex128)
502
+ alloc_4pcfreal = 0
503
+ if hasintegratedstats:
504
+ if mapradii is None:
505
+ raise ValueError("Aperture radii need to be specified in variable `mapradii`.")
506
+ mapradii = mapradii.astype(np.float64)
507
+ M4correlators = np.zeros(8*self.nzcombis*len(mapradii)).astype(np.complex128)
508
+ else:
509
+ mapradii = __fillflag*np.ones(__lenflag).astype(np.float64)
510
+ N4correlators = __fillflag*np.ones(__lenflag).astype(np.complex128)
511
+
512
+ # Build args based on chosen methods
513
+ if self.method=="Discrete" and not lowmem:
514
+ args_basesetup = (np.int32(_nmax), np.float64(self.min_sep),
515
+ np.float64(self.max_sep), np.array([-1.]).astype(np.float64),
516
+ np.int32(self.nbinsr), np.int32(self.multicountcorr), )
517
+ args = (*args_basecat,
518
+ *args_basesetup,
519
+ *args_hash,
520
+ np.int32(self.nthreads),
521
+ np.int32(self._verbose_c+self._verbose_debug),
522
+ bin_centers,
523
+ Upsilon_n,
524
+ N_n)
525
+ func = self.clib.alloc_notomoGammans_discrete_gggg
526
+ if self.method=="Discrete" and lowmem:
527
+ _resradial = gen_thetacombis_fourthorder(nbinsr=self.nbinsr, nthreads=self.nthreads, batchsize=batchsize,
528
+ batchsize_max=self.thetabatchsize_max, ordered=True, custom=custom_thetacombis,
529
+ verbose=self._verbose_python)
530
+ _, _, thetacombis_batches, cumnthetacombis_batches, nthetacombis_batches, nbatches = _resradial
531
+
532
+ args_basesetup = (np.int32(_nmax),
533
+ np.float64(self.min_sep), np.float64(self.max_sep), np.int32(self.nbinsr),
534
+ np.int32(self.multicountcorr),
535
+ self.phis[0].astype(np.float64),
536
+ 2*np.pi/_nphis*np.ones(_nphis, dtype=np.float64), np.int32(_nphis))
537
+ args_4pcf = (np.int32(alloc_4pcfmultipoles), np.int32(alloc_4pcfreal),
538
+ bin_centers, Upsilon_n, N_n, fourpcf, fourpcf_norm, )
539
+ args_thetas = (thetacombis_batches, nthetacombis_batches, cumnthetacombis_batches, nbatches, )
540
+ args_map4 = (mapradii, np.int32(len(mapradii)), M4correlators)
541
+ args = (*args_basecat,
542
+ *args_basesetup,
543
+ *args_hash,
544
+ *args_thetas,
545
+ np.int32(self.nthreads),
546
+ np.int32(self._verbose_c+self._verbose_debug),
547
+ i_projection,
548
+ *args_map4,
549
+ *args_4pcf)
550
+ func = self.clib.alloc_notomoMap4_disc_gggg
551
+ if self.method=="Tree":
552
+ # Prepare mask for nonredundant theta- and multipole configurations
553
+ _resradial = gen_thetacombis_fourthorder(nbinsr=self.nbinsr, nthreads=self.nthreads, batchsize=batchsize,
554
+ batchsize_max=self.thetabatchsize_max, ordered=True, custom=custom_thetacombis,
555
+ verbose=self._verbose_python*lowmem)
556
+ _, _, thetacombis_batches, cumnthetacombis_batches, nthetacombis_batches, nbatches = _resradial
557
+ assert(self.nmaxs[0]==self.nmaxs[1])
558
+ _resmultipoles = gen_n2n3indices_Upsfourth(self.nmaxs[0])
559
+ _shape, _inds, _n2s, _n3s = _resmultipoles
560
+
561
+ # Prepare reduced catalogs
562
+ cutfirst = np.int32(self.tree_resos[0]==0.)
563
+ mhash = cat.multihash(dpixs=self.tree_resos[cutfirst:], dpix_hash=self.tree_resos[-1],
564
+ shuffle=self.shuffle_pix, w2field=True, normed=True)
565
+ (ngal_resos, pos1s, pos2s, weights, zbins, isinners, allfields,
566
+ index_matchers, pixs_galind_bounds, pix_gals, dpixs1_true, dpixs2_true) = mhash
567
+ weight_resos = np.concatenate(weights).astype(np.float64)
568
+ pos1_resos = np.concatenate(pos1s).astype(np.float64)
569
+ pos2_resos = np.concatenate(pos2s).astype(np.float64)
570
+ zbin_resos = np.concatenate(zbins).astype(np.int32)
571
+ isinner_resos = np.concatenate(isinners).astype(np.float64)
572
+ e1_resos = np.concatenate([allfields[i][0] for i in range(len(allfields))]).astype(np.float64)
573
+ e2_resos = np.concatenate([allfields[i][1] for i in range(len(allfields))]).astype(np.float64)
574
+ index_matcher_resos = np.concatenate(index_matchers).astype(np.int32)
575
+ pixs_galind_bounds_resos = np.concatenate(pixs_galind_bounds).astype(np.int32)
576
+ pix_gals_resos = np.concatenate(pix_gals).astype(np.int32)
577
+ index_matcher_flat = np.argwhere(cat.index_matcher>-1).flatten()
578
+ nregions = len(index_matcher_flat)
579
+ if not lowmem:
580
+ args_basesetup = (np.int32(_nmax),
581
+ np.float64(self.min_sep), np.float64(self.max_sep), np.int32(self.nbinsr),
582
+ np.int32(cumnthetacombis_batches[-1]), np.int32(self.multicountcorr),
583
+ _inds, np.int32(len(_inds)),)
584
+ args_resos = (np.int32(self.tree_nresos), self.tree_redges, np.array(ngal_resos, dtype=np.int32),
585
+ isinner_resos, weight_resos, pos1_resos, pos2_resos, e1_resos, e2_resos,
586
+ index_matcher_resos, pixs_galind_bounds_resos, pix_gals_resos, np.int32(nregions), )
587
+ args_hash = (np.float64(cat.pix1_start), np.float64(cat.pix1_d), np.int32(cat.pix1_n),
588
+ np.float64(cat.pix2_start), np.float64(cat.pix2_d), np.int32(cat.pix2_n), )
589
+ args_out = ( bin_centers, Upsilon_n, N_n, )
590
+ args = (*args_basecat,
591
+ *args_basesetup,
592
+ *args_resos,
593
+ *args_hash,
594
+ np.int32(self.nthreads),
595
+ np.int32(self._verbose_c+self._verbose_debug),
596
+ *args_out)
597
+ func = self.clib.alloc_notomoGammans_tree_gggg
598
+ if lowmem:
599
+ # Build args
600
+ args_basesetup = (np.int32(_nmax),
601
+ np.float64(self.min_sep), np.float64(self.max_sep), np.int32(self.nbinsr),
602
+ np.int32(self.multicountcorr),
603
+ _inds, np.int32(len(_inds)), self.phis[0].astype(np.float64),
604
+ 2*np.pi/_nphis*np.ones(_nphis, dtype=np.float64), np.int32(_nphis), )
605
+ args_resos = (np.int32(self.tree_nresos), self.tree_redges, np.array(ngal_resos, dtype=np.int32),
606
+ isinner_resos, weight_resos, pos1_resos, pos2_resos, e1_resos, e2_resos,
607
+ index_matcher_resos, pixs_galind_bounds_resos, pix_gals_resos, np.int32(nregions), )
608
+ args_hash = (np.float64(cat.pix1_start), np.float64(cat.pix1_d), np.int32(cat.pix1_n),
609
+ np.float64(cat.pix2_start), np.float64(cat.pix2_d), np.int32(cat.pix2_n), )
610
+ args_thetas = (thetacombis_batches, nthetacombis_batches, cumnthetacombis_batches, nbatches, )
611
+ args_map4 = (mapradii, np.int32(len(mapradii)), M4correlators)
612
+ args_4pcf = (np.int32(alloc_4pcfmultipoles), np.int32(alloc_4pcfreal),
613
+ bin_centers, Upsilon_n, N_n, fourpcf, fourpcf_norm, )
614
+ args = (*args_basecat,
615
+ *args_basesetup,
616
+ *args_resos,
617
+ *args_hash,
618
+ *args_thetas,
619
+ np.int32(self.nthreads),
620
+ np.int32(self._verbose_c+self._verbose_debug),
621
+ i_projection,
622
+ *args_map4,
623
+ *args_4pcf)
624
+ func = self.clib.alloc_notomoMap4_tree_gggg
625
+
626
+ # Optionally print the arguments
627
+ if self._verbose_debug:
628
+ print("We pass the following arguments:")
629
+ for elarg, arg in enumerate(args):
630
+ toprint = (elarg, type(arg),)
631
+ if isinstance(arg, np.ndarray):
632
+ toprint += (type(arg[0]), arg.shape)
633
+ try:
634
+ toprint += (func.argtypes[elarg], )
635
+ print(toprint)
636
+ print(arg)
637
+ except:
638
+ print("We did have a problem for arg %i"%elarg)
639
+
640
+ ## Compute 4th order stats ##
641
+ func(*args)
642
+ self.projection = projection
643
+
644
+ ## Massage the output ##
645
+ istatout = ()
646
+ self.bin_centers = bin_centers.reshape(szr)
647
+ self.bin_centers_mean = np.mean(self.bin_centers, axis=0)
648
+ if "4pcf_multipole" in statistics:
649
+ self.npcf_multipoles = Upsilon_n.reshape(sc)
650
+ self.npcf_multipoles_norm = N_n.reshape(sn)
651
+ if "4pcf_real" in statistics:
652
+ if lowmem:
653
+ self.npcf = fourpcf.reshape(s4pcf)
654
+ self.npcf_norm = fourpcf_norm.reshape(s4pcfn)
655
+ else:
656
+ if self._verbose_python:
657
+ print("Transforming output to real space basis")
658
+ self.multipoles2npcf_c(projection=projection)
659
+ if hasintegratedstats:
660
+ if "M4" in statistics:
661
+ istatout += (M4correlators.reshape((8,self.nzcombis,len(mapradii))), )
662
+ # TODO allocate map4, map4c etc.
663
+
664
+ return istatout
665
+
666
+ def multipoles2npcf_c(self, projection="X"):
667
+ r""" Converts a 4PCF in the multipole basis in the real space basis.
668
+ """
669
+ assert((projection in self.proj_dict.keys()) and (projection in self.projections_avail))
670
+
671
+ _nzero1 = self.nmaxs[0]
672
+ _nzero2 = self.nmaxs[1]
673
+ _phis1 = self.phis[0].astype(np.float64)
674
+ _phis2 = self.phis[1].astype(np.float64)
675
+ _nphis1 = len(self.phis[0])
676
+ _nphis2 = len(self.phis[1])
677
+ ncfs, nnvals, _, nzcombis, nbinsr, _, _ = np.shape(self.npcf_multipoles)
678
+
679
+ shape_npcf = (self.n_cfs, nzcombis, nbinsr, nbinsr, nbinsr, _nphis1, _nphis2)
680
+ shape_npcf_norm = (nzcombis, nbinsr, nbinsr, nbinsr, _nphis1, _nphis2)
681
+ self.npcf = np.zeros(self.n_cfs*nzcombis*nbinsr*nbinsr*nbinsr*_nphis1*_nphis2, dtype=np.complex128)
682
+ self.npcf_norm = np.zeros(nzcombis*nbinsr*nbinsr*nbinsr*_nphis1*_nphis2, dtype=np.complex128)
683
+ self.clib.multipoles2npcf_gggg(self.npcf_multipoles.flatten(), self.npcf_multipoles_norm.flatten(),
684
+ self.bin_centers_mean.astype(np.float64), np.int32(self.proj_dict[projection]),
685
+ 8, nbinsr, self.nmaxs[0].astype(np.int32), _phis1, _nphis1, _phis2, _nphis2,
686
+ self.nthreads, self.npcf, self.npcf_norm)
687
+ self.npcf = self.npcf.reshape(shape_npcf)
688
+ self.npcf_norm = self.npcf_norm.reshape(shape_npcf_norm)
689
+ self.projection = projection
690
+
691
+
692
+ def multipoles2npcf_singlethetcombi(self, elthet1, elthet2, elthet3, projection="X"):
693
+ r""" Converts a 4PCF in the multipole basis in the real space basis for a fixed combination of radial bins.
694
+
695
+ Returns:
696
+ --------
697
+ npcf_out: np.ndarray
698
+ Natural 4PCF components in the real-space bassi for all angular combinations.
699
+ npcf_norm_out: np.ndarray
700
+ 4PCF weighted counts in the real-space bassi for all angular combinations.
701
+ """
702
+ assert((projection in self.proj_dict.keys()) and (projection in self.projections_avail))
703
+
704
+ _phis1 = self.phis[0].astype(np.float64)
705
+ _phis2 = self.phis[1].astype(np.float64)
706
+ _nphis1 = len(self.phis[0])
707
+ _nphis2 = len(self.phis[1])
708
+ ncfs, nnvals, _, nzcombis, nbinsr, _, _ = np.shape(self.npcf_multipoles)
709
+
710
+ Upsilon_in = self.npcf_multipoles[...,elthet1,elthet2,elthet3].flatten()
711
+ N_in = self.npcf_multipoles_norm[...,elthet1,elthet2,elthet3].flatten()
712
+ npcf_out = np.zeros(self.n_cfs*nzcombis*_nphis1*_nphis2, dtype=np.complex128)
713
+ npcf_norm_out = np.zeros(nzcombis*_nphis1*_nphis2, dtype=np.complex128)
714
+
715
+ self.clib.multipoles2npcf_gggg_singletheta(
716
+ Upsilon_in, N_in, self.nmaxs[0], self.nmaxs[1],
717
+ self.bin_centers_mean[elthet1], self.bin_centers_mean[elthet2], self.bin_centers_mean[elthet3],
718
+ _phis1, _phis2, _nphis1, _nphis2,
719
+ np.int32(self.proj_dict[projection]), npcf_out, npcf_norm_out)
720
+
721
+ return npcf_out.reshape((self.n_cfs, _nphis1,_nphis2)), npcf_norm_out.reshape((_nphis1,_nphis2))
722
+
723
+ def multipoles2npcf_gggg_singletheta_nconvergence(self, elthet1, elthet2, elthet3, projection="X"):
724
+ r""" Checks convergence of the conversion between mutltipole-space and real space for a combination of radial bins.
725
+
726
+ Returns:
727
+ --------
728
+ npcf_out: np.ndarray
729
+ Natural 4PCF components in the real-space basis for all angular combinations.
730
+ npcf_norm_out: np.ndarray
731
+ 4PCF weighted counts in the real-space basis for all angular combinations.
732
+ """
733
+ assert((projection in self.proj_dict.keys()) and (projection in self.projections_avail))
734
+
735
+ _phis1 = self.phis[0].astype(np.float64)
736
+ _phis2 = self.phis[1].astype(np.float64)
737
+ _nphis1 = len(self.phis[0])
738
+ _nphis2 = len(self.phis[1])
739
+
740
+ ncfs, nnvals, _, nzcombis, nbinsr, _, _ = np.shape(self.npcf_multipoles)
741
+
742
+ Upsilon_in = self.npcf_multipoles[...,elthet1,elthet2,elthet3].flatten()
743
+ N_in = self.npcf_multipoles_norm[...,elthet1,elthet2,elthet3].flatten()
744
+ npcf_out = np.zeros(self.n_cfs*nzcombis*(self.nmaxs[0]+1)*(self.nmaxs[1]+1)*_nphis1*_nphis2, dtype=np.complex128)
745
+ npcf_norm_out = np.zeros(nzcombis*(self.nmaxs[0]+1)*(self.nmaxs[1]+1)*_nphis1*_nphis2, dtype=np.complex128)
746
+
747
+ self.clib.multipoles2npcf_gggg_singletheta_nconvergence(
748
+ Upsilon_in, N_in, self.nmaxs[0], self.nmaxs[1],
749
+ self.bin_centers_mean[elthet1], self.bin_centers_mean[elthet2], self.bin_centers_mean[elthet3],
750
+ _phis1, _phis2, _nphis1, _nphis2,
751
+ np.int32(self.proj_dict[projection]), npcf_out, npcf_norm_out)
752
+
753
+ npcf_out = npcf_out.reshape((self.n_cfs, self.nmaxs[0]+1, self.nmaxs[1]+1, _nphis1, _nphis2))
754
+ npcf_norm_out = npcf_norm_out.reshape((self.nmaxs[0]+1, self.nmaxs[1]+1, _nphis1, _nphis2))
755
+
756
+ return npcf_out, npcf_norm_out
757
+
758
+ def computeMap4(self, radii, nmax_trafo=None, basis='MapMx'):
759
+ r"""Computes the fourth-order aperture mass statistcs using the polynomial filter of Crittenden 2002."""
760
+
761
+ assert(basis in ['MapMx','MM*','both'])
762
+
763
+ if nmax_trafo is None:
764
+ nmax_trafo=self.nmaxs[0]
765
+
766
+ # Retrieve all the aperture measures in the MM* basis via the 5D transformation eqns
767
+ M4correlators = np.zeros(8*len(radii), dtype=np.complex128)
768
+ self.clib.fourpcfmultipoles2M4correlators(
769
+ np.int32(self.nmaxs[0]), np.int32(nmax_trafo),
770
+ self.bin_edges, self.bin_centers_mean, np.int32(self.nbinsr),
771
+ radii.astype(np.float64), np.int32(len(radii)),
772
+ self.phis[0].astype(np.float64), self.phis[1].astype(np.float64),
773
+ self.dphis[0].astype(np.float64), self.dphis[1].astype(np.float64),
774
+ len(self.phis[0]), len(self.phis[1]),
775
+ np.int32(self.proj_dict[self.projection]), np.int32(self.nthreads),
776
+ self.npcf_multipoles.flatten(), self.npcf_multipoles_norm.flatten(),
777
+ M4correlators)
778
+ res_MMStar = M4correlators.reshape((8,len(radii)))
779
+
780
+ # Allocate result
781
+ res = ()
782
+ if basis=='MM*' or basis=='both':
783
+ res += (res_MMStar, )
784
+ if basis=='MapMx' or basis=='both':
785
+ res += ( GGGGCorrelation_NoTomo.MMStar2MapMx_fourth(res_MMStar), )
786
+
787
+ return res
788
+
789
+ ## PROJECTIONS ##
790
+ def projectnpcf(self, projection):
791
+ super()._projectnpcf(self, projection)
792
+
793
+ def _x2centroid(self):
794
+ gammas_cen = np.zeros_like(self.npcf)
795
+ pimod = lambda x: x%(2*np.pi) - 2*np.pi*(x%(2*np.pi)>=np.pi)
796
+ npcf_cen = np.zeros(self.npcf.shape, dtype=complex)
797
+ _centers = np.mean(self.bin_centers, axis=0)
798
+ for elb1, bin1 in enumerate(_centers):
799
+ for elb2, bin2 in enumerate(_centers):
800
+ for elb3, bin3 in enumerate(_centers):
801
+ phiexp = np.exp(1J*self.phis[0])
802
+ phiexp_c = np.exp(-1J*self.phis[0])
803
+ phi12grid, phi13grid = np.meshgrid(phiexp, phiexp)
804
+ phi12grid_c, phi13grid_c = np.meshgrid(phiexp_c, phiexp_c)
805
+ prod1 = (bin1 +bin2*phi12grid_c + bin3*phi13grid_c) /(bin1 + bin2*phi12grid + bin3*phi13grid) #q1
806
+ prod2 = (3*bin1 -bin2*phi12grid_c - bin3*phi13grid_c) /(3*bin1 - bin2*phi12grid - bin3*phi13grid) #q2
807
+ prod3 = (bin1 -3*bin2*phi12grid_c + bin3*phi13grid_c) /(bin1 - 3*bin2*phi12grid + bin3*phi13grid) #q3
808
+ prod4 = (bin1 +bin2*phi12grid_c - 3*bin3*phi13grid_c)/(bin1 + bin2*phi12grid - 3*bin3*phi13grid) #q4
809
+ prod1_inv = prod1.conj()/np.abs(prod1)
810
+ prod2_inv = prod2.conj()/np.abs(prod2)
811
+ prod3_inv = prod3.conj()/np.abs(prod3)
812
+ prod4_inv = prod4.conj()/np.abs(prod4)
813
+ rot_nom = np.zeros((8,len(self.phis[0]), len(self.phis[1])))
814
+ rot_nom[0] = pimod(np.angle(prod1 *prod2 *prod3 *prod4 * phi12grid**2 * phi13grid**3))
815
+ rot_nom[1] = pimod(np.angle(prod1_inv*prod2 *prod3 *prod4 * phi12grid**2 * phi13grid**1))
816
+ rot_nom[2] = pimod(np.angle(prod1 *prod2_inv*prod3 *prod4 * phi12grid**2 * phi13grid**3))
817
+ rot_nom[3] = pimod(np.angle(prod1 *prod2 *prod3_inv*prod4 * phi12grid_c**2 * phi13grid**3))
818
+ rot_nom[4] = pimod(np.angle(prod1 *prod2 *prod3 *prod4_inv * phi12grid**2 * phi13grid_c**1))
819
+ rot_nom[5] = pimod(np.angle(prod1_inv*prod2_inv*prod3 *prod4 * phi12grid**2 * phi13grid**1))
820
+ rot_nom[6] = pimod(np.angle(prod1_inv*prod2 *prod3_inv*prod4 * phi12grid_c**2 * phi13grid**1))
821
+ rot_nom[7] = pimod(np.angle(prod1_inv*prod2 *prod3 *prod4_inv * phi12grid**2 * phi13grid_c**3))
822
+ gammas_cen[:,:,elb1,elb2,elb3] = self.npcf[:,:,elb1,elb2,elb3]*np.exp(1j*rot_nom)[:,np.newaxis,:,:]
823
+ return gammas_cen
824
+
825
+ ## GAUSSIAN-FIELD SPECIFIC FUNCTIONS ##
826
+ # Deprecate this as it has been ported to c
827
+ @staticmethod
828
+ def fourpcf_gauss_x(theta1, theta2, theta3, phi12, phi13, xipspl, ximspl):
829
+ """ Computes disconnected part of the 4pcf in the 'x'-projection
830
+ given a splined 2pcf
831
+ """
832
+ allgammas = [None]*8
833
+ xprojs = [None]*8
834
+ y1 = theta1 * np.ones_like(phi12)
835
+ y2 = theta2*np.exp(1j*phi12)
836
+ y3 = theta3*np.exp(1j*phi13)
837
+ absy1 = np.abs(y1)
838
+ absy2 = np.abs(y2)
839
+ absy3 = np.abs(y3)
840
+ absy12 = np.abs(y2-y1)
841
+ absy13 = np.abs(y1-y3)
842
+ absy23 = np.abs(y3-y2)
843
+ q1 = -0.25*(y1+y2+y3)
844
+ q2 = 0.25*(3*y1-y2-y3)
845
+ q3 = 0.25*(3*y2-y3-y1)
846
+ q4 = 0.25*(3*y3-y1-y2)
847
+ q1c = q1.conj(); q2c = q2.conj(); q3c = q3.conj(); q4c = q4.conj();
848
+ y123_cub = (np.abs(y1)*np.abs(y2)*np.abs(y3))**3
849
+ ang1_4 = ((y1)/absy1)**4; ang2_4 = ((y2)/absy2)**4; ang3_4 = ((y3)/absy3)**4
850
+ ang12_4 = ((y2-y1)/absy12)**4; ang13_4 = ((y3-y1)/absy13)**4; ang23_4 = ((y3-y2)/absy23)**4;
851
+ xprojs[0] = (y1**3*y2**2*y3**3)/(np.abs(y1)**3*np.abs(y2)**2*np.abs(y3)**3)
852
+ xprojs[1] = (y1**1*y2**2*y3**1)/(np.abs(y1)**1*np.abs(y2)**2*np.abs(y3)**1)
853
+ xprojs[2] = (y1**-1*y2**2*y3**3)/(np.abs(y1)**-1*np.abs(y2)**2*np.abs(y3)**3)
854
+ xprojs[3] = (y1**3*y2**-2*y3**3)/(np.abs(y1)**3*np.abs(y2)**-2*np.abs(y3)**3)
855
+ xprojs[4] = (y1**3*y2**2*y3**-1)/(np.abs(y1)**3*np.abs(y2)**2*np.abs(y3)**-1)
856
+ xprojs[5] = (y1**-3*y2**2*y3**1)/(np.abs(y1)**-3*np.abs(y2)**2*np.abs(y3)**1)
857
+ xprojs[6] = (y1**1*y2**-2*y3**1)/(np.abs(y1)**1*np.abs(y2)**-2*np.abs(y3)**1)
858
+ xprojs[7] = (y1**1*y2**2*y3**-3)/(np.abs(y1)**1*np.abs(y2)**2*np.abs(y3)**-3)
859
+ allgammas[0] = 1./xprojs[0] * (
860
+ ang23_4 * ang1_4 * ximspl(absy23) * ximspl(absy1) +
861
+ ang13_4 * ang2_4 * ximspl(absy13) * ximspl(absy2) +
862
+ ang12_4 * ang3_4 * ximspl(absy12) * ximspl(absy3))
863
+ allgammas[1] = 1./xprojs[1] * (
864
+ ang23_4 * xipspl(absy1) * ximspl(absy23) +
865
+ ang13_4 * xipspl(absy2) * ximspl(absy13) +
866
+ ang12_4 * xipspl(absy3) * ximspl(absy12))
867
+ allgammas[2] = 1./xprojs[2] * (
868
+ ang23_4 * xipspl(absy1) * ximspl(absy23) +
869
+ ang2_4 * ximspl(absy2) * xipspl(absy13) +
870
+ ang3_4 * ximspl(absy3) * xipspl(absy12))
871
+ allgammas[3] = 1./xprojs[3] * (
872
+ ang1_4 * ximspl(absy1) * xipspl(absy23) +
873
+ ang13_4 * xipspl(absy2) * ximspl(absy13) +
874
+ ang3_4 * ximspl(absy3) * xipspl(absy12))
875
+ allgammas[4] = 1./xprojs[4] * (
876
+ ang1_4 * ximspl(absy1) * xipspl(absy23) +
877
+ ang2_4 * ximspl(absy2) * xipspl(absy13) +
878
+ ang12_4 * xipspl(absy3) * ximspl(absy12))
879
+ allgammas[5] = 1./xprojs[5] * (
880
+ ang1_4.conj() * ang23_4 * ximspl(absy23) * ximspl(absy1) +
881
+ xipspl(absy13) * xipspl(absy2) +
882
+ xipspl(absy12) * xipspl(absy3))
883
+ allgammas[6] = 1./xprojs[6] * (
884
+ xipspl(absy23) * xipspl(absy1) +
885
+ ang2_4.conj() * ang13_4 * ximspl(absy13) * ximspl(absy2) +
886
+ xipspl(absy12) * xipspl(absy3))
887
+ allgammas[7] = 1./xprojs[7] * (
888
+ xipspl(absy23) * xipspl(absy1) +
889
+ xipspl(absy13) * xipspl(absy2) +
890
+ ang3_4.conj() * ang12_4 * ximspl(absy12) * ximspl(absy3))
891
+
892
+ return allgammas
893
+
894
+ # Disconnected 4pcf from binned 2pcf (might want to deprecate this as it is a special case of nsubr==1)
895
+ def __gauss4pcf_analytic(self, theta1, theta2, theta3, xip_arr, xim_arr, thetamin_xi, thetamax_xi, dtheta_xi):
896
+ gausss_4pcf = np.zeros(8*len(self.phis[0])*len(self.phis[0]),dtype=np.complex128)
897
+ self.clib.gauss4pcf_analytic(theta1.astype(np.float64),
898
+ theta2.astype(np.float64),
899
+ theta3.astype(np.float64),
900
+ self.phis[0].astype(np.float64), np.int32(len(self.phis[0])),
901
+ xip_arr.astype(np.float64), xim_arr.astype(np.float64),
902
+ thetamin_xi, thetamax_xi, dtheta_xi,
903
+ gausss_4pcf)
904
+ return gausss_4pcf
905
+
906
+
907
+ # [Debug] Disconnected 4pcf from analytic 2pcf
908
+ def gauss4pcf_analytic(self, itheta1, itheta2, itheta3, nsubr,
909
+ xip_arr, xim_arr, thetamin_xi, thetamax_xi, dtheta_xi):
910
+
911
+ gauss_4pcf = np.zeros(8*self.nbinsphi[0]*self.nbinsphi[1],dtype=np.complex128)
912
+
913
+ self.clib.gauss4pcf_analytic_integrated(
914
+ np.int32(itheta1),
915
+ np.int32(itheta2),
916
+ np.int32(itheta3),
917
+ np.int32(nsubr),
918
+ self.bin_edges.astype(np.float64),
919
+ np.int32(self.nbinsr),
920
+ self.phis[0].astype(np.float64),
921
+ np.int32(self.nbinsphi[0]),
922
+ xip_arr.astype(np.float64),
923
+ xim_arr.astype(np.float64),
924
+ np.float64(thetamin_xi),
925
+ np.float64(thetamax_xi),
926
+ np.float64(dtheta_xi),
927
+ gauss_4pcf)
928
+ return gauss_4pcf.reshape((8, self.nbinsphi[0], self.nbinsphi[1]))
929
+
930
+ # Compute disconnected part of 4pcf in multiple basis
931
+ def gauss4pcf_multipolebasis(self, itheta1, itheta2, itheta3, nsubr,
932
+ xip_arr, xim_arr, thetamin_xi, thetamax_xi, dtheta_xi):
933
+
934
+ # Obtain integrated 4pcf
935
+ int_4pcf = self.gauss4pcf_analytic_integrated(itheta1, itheta2, itheta3, nsubr,
936
+ xip_arr, xim_arr,
937
+ thetamin_xi, thetamax_xi, dtheta_xi)
938
+
939
+ # Transform to multiple basis (cf eq xxx in P25)
940
+ phigrid1, phigrid2 = np.meshgrid(self.phis[0],self.phis[1])
941
+ gauss_multipoles = np.zeros((8,2*self.nmaxs[0]+1,2*self.nmaxs[1]+1),dtype=complex)
942
+ for eln2,n2 in enumerate(np.arange(-self.nmaxs[0],self.nmaxs[0]+1)):
943
+ fac1 = np.e**(-1J*n2*phigrid1)
944
+ for eln3,n3 in enumerate(np.arange(-self.nmaxs[1],self.nmaxs[1]+1)):
945
+ fac2 = np.e**(-1J*n3*phigrid2)
946
+ for elcomp in range(8):
947
+ gauss_multipoles[elcomp,eln2,eln3] = np.mean(int_4pcf[elcomp]*fac1*fac2)
948
+
949
+ return gauss_multipoles
950
+
951
+
952
+ def estimateMap4disc(self, cat, radii, basis='MapMx',fac_minsep=0.05, fac_maxsep=2., binsize=0.1, nsubr=3, nsubsample_filter=1):
953
+ """ Estimate disconnected part of fourth-order aperture statistics on a shape catalog. """
954
+
955
+ # Compute shear 2pcf from data
956
+ min_sep_disc = fac_minsep*self.min_sep
957
+ max_sep_disc = fac_maxsep*self.max_sep
958
+ binsize_disc = min(0.1,self.binsize)
959
+ ggcorr = GGCorrelation(min_sep=min_sep_disc, max_sep=max_sep_disc,binsize=binsize_disc,
960
+ rmin_pixsize=self.rmin_pixsize, tree_resos=self.tree_resos, nthreads=self.nthreads)
961
+ ggcorr.process(cat)
962
+
963
+ # Convert this to fourth-order aperture statistics
964
+ linarr = np.linspace(min_sep_disc,max_sep_disc,int(max_sep_disc/(binsize_disc*min_sep_disc)))
965
+ xip_spl = interp1d(x=ggcorr.bin_centers_mean,y=ggcorr.xip[0].real,fill_value=0,bounds_error=False)
966
+ xim_spl = interp1d(x=ggcorr.bin_centers_mean,y=ggcorr.xim[0].real,fill_value=0,bounds_error=False)
967
+ mapstat = self.Map4analytic(mapradii=radii,
968
+ xip_spl=xip_spl,
969
+ xim_spl=xim_spl,
970
+ thetamin_xi=linarr[0],
971
+ thetamax_xi=linarr[-1],
972
+ ntheta_xi=len(linarr),
973
+ nsubr=nsubr,nsubsample_filter=nsubsample_filter,basis=basis)
974
+ return mapstat
975
+
976
+
977
+ # Disconnected part of Map^4 from analytic 2pcf
978
+ # thetamin_xi, thetamax_xi, ntheta_xi is the linspaced array in which the xipm are passed to the external function
979
+ def Map4analytic(self, mapradii, xip_spl, xim_spl, thetamin_xi, thetamax_xi, ntheta_xi,
980
+ nsubr=1, nsubsample_filter=1, batchsize=None, basis='MapMx'):
981
+
982
+ self.nbinsz = 1
983
+ self.nzcombis = 1
984
+ _nmax = self.nmaxs[0]
985
+ _nnvals = (2*_nmax+1)*(2*_nmax+1)
986
+ _nbinsr3 = self.nbinsr*self.nbinsr*self.nbinsr
987
+ _nphis = len(self.phis[0])
988
+ bin_centers = np.zeros(self.nbinsz*self.nbinsr).astype(np.float64)
989
+ M4correlators = np.zeros(8*self.nzcombis*len(mapradii)).astype(np.complex128)
990
+ # Define the radial bin batches
991
+ if batchsize is None:
992
+ batchsize = min(_nbinsr3,min(10000,int(_nbinsr3/self.nthreads)))
993
+ if self._verbose_python:
994
+ print("Using batchsize of %i for radial bins"%batchsize)
995
+ nbatches = np.int32(_nbinsr3/batchsize)
996
+ thetacombis_batches = np.arange(_nbinsr3).astype(np.int32)
997
+ cumnthetacombis_batches = (np.arange(nbatches+1)*_nbinsr3/(nbatches)).astype(np.int32)
998
+ nthetacombis_batches = (cumnthetacombis_batches[1:]-cumnthetacombis_batches[:-1]).astype(np.int32)
999
+ cumnthetacombis_batches[-1] = _nbinsr3
1000
+ nthetacombis_batches[-1] = _nbinsr3-cumnthetacombis_batches[-2]
1001
+ thetacombis_batches = thetacombis_batches.flatten().astype(np.int32)
1002
+ nbatches = len(nthetacombis_batches)
1003
+
1004
+ args_4pcfsetup = (np.float64(self.min_sep), np.float64(self.max_sep), np.int32(self.nbinsr),
1005
+ self.phis[0].astype(np.float64),
1006
+ (self.phis[0][1]-self.phis[0][0])*np.ones(_nphis, dtype=np.float64), _nphis, np.int32(nsubr), )
1007
+ args_thetas = (thetacombis_batches, nthetacombis_batches, cumnthetacombis_batches, nbatches, )
1008
+ args_map4 = (mapradii.astype(np.float64), np.int32(len(mapradii)), )
1009
+ thetas_xi = np.linspace(thetamin_xi,thetamax_xi,ntheta_xi+1)
1010
+ args_xi = (xip_spl(thetas_xi), xim_spl(thetas_xi), thetamin_xi, thetamax_xi, ntheta_xi, nsubsample_filter, )
1011
+ args = (*args_4pcfsetup,
1012
+ *args_thetas,
1013
+ np.int32(self.nthreads),
1014
+ *args_map4,
1015
+ *args_xi,
1016
+ M4correlators)
1017
+ func = self.clib.alloc_notomoMap4_analytic
1018
+
1019
+ if self._verbose_debug:
1020
+ for elarg, arg in enumerate(args):
1021
+ toprint = (elarg, type(arg),)
1022
+ if isinstance(arg, np.ndarray):
1023
+ toprint += (type(arg[0]), arg.shape)
1024
+ try:
1025
+ toprint += (func.argtypes[elarg], )
1026
+ print(toprint)
1027
+ print(arg)
1028
+ except:
1029
+ print("We did have a problem for arg %i"%elarg)
1030
+
1031
+ func(*args)
1032
+
1033
+ res_MMStar = M4correlators.reshape((8,len(mapradii)))
1034
+ # Allocate result
1035
+ res = ()
1036
+ if basis=='MM*' or basis=='both':
1037
+ res += (res_MMStar, )
1038
+ if basis=='MapMx' or basis=='both':
1039
+ res += (GGGGCorrelation_NoTomo.MMStar2MapMx_fourth(res_MMStar), )
1040
+
1041
+ return res
1042
+
1043
+ def getMultipolesFromSymm(self, nmax_rec, itheta1, itheta2, itheta3, eltrafo):
1044
+
1045
+ nmax_alloc = 2*nmax_rec+1
1046
+ assert(nmax_alloc<=self.nmaxs[0])
1047
+
1048
+ # Only select relevant n1/n2 indices
1049
+ _dn = self.nmaxs[0]-nmax_alloc
1050
+
1051
+ _shape, _inds, _n2s, _n3s = gen_n2n3indices_Upsfourth(nmax_rec)
1052
+ Upsn_in = self.npcf_multipoles[:,_dn:-_dn,_dn:-_dn,0,itheta1,itheta2,itheta3].flatten()
1053
+ Nn_in = self.npcf_multipoles_norm[_dn:-_dn,_dn:-_dn,0,itheta1,itheta2,itheta3].flatten()
1054
+ Upsn_out = np.zeros(8*(2*nmax_rec+1)*(2*nmax_rec+1), dtype=np.complex128)
1055
+ Nn_out = np.zeros(1*(2*nmax_rec+1)*(2*nmax_rec+1), dtype=np.complex128)
1056
+
1057
+ self.clib.getMultipolesFromSymm(
1058
+ Upsn_in, Nn_in, nmax_rec, eltrafo, _inds, len(_inds), Upsn_out, Nn_out)
1059
+
1060
+ Upsn_out = Upsn_out.reshape((8,(2*nmax_rec+1),(2*nmax_rec+1)))
1061
+ Nn_out = Nn_out.reshape(((2*nmax_rec+1),(2*nmax_rec+1)))
1062
+
1063
+ return Upsn_out, Nn_out
1064
+
1065
+ ## MISC HELPERS ##
1066
+ @staticmethod
1067
+ def MMStar2MapMx_fourth(res_MMStar):
1068
+ """ Transforms fourth-order aperture correlators to fourth-order aperture mass.
1069
+ See i.e. Eqs (32)-(36) in Silvestre-Rosello+ 2025 (arxiv.org/pdf/2509.07973).
1070
+ """
1071
+ res_MapMx = np.zeros((16,*res_MMStar.shape[1:]))
1072
+ Mcorr2Map4_re = .125*np.array([[+1,+1,+1,+1,+1,+1,+1,+1],
1073
+ [-1,-1,-1,+1,+1,-1,+1,+1],
1074
+ [-1,-1,+1,-1,+1,+1,-1,+1],
1075
+ [-1,-1,+1,+1,-1,+1,+1,-1],
1076
+ [-1,+1,-1,-1,+1,+1,+1,-1],
1077
+ [-1,+1,-1,+1,-1,+1,-1,+1],
1078
+ [-1,+1,+1,-1,-1,-1,+1,+1],
1079
+ [+1,-1,-1,-1,-1,+1,+1,+1]])
1080
+ Mcorr2Map4_im = .125*np.array([[+1,-1,+1,+1,+1,-1,-1,-1],
1081
+ [+1,+1,-1,+1,+1,-1,+1,+1],
1082
+ [+1,+1,+1,-1,+1,+1,-1,+1],
1083
+ [+1,+1,+1,+1,-1,+1,+1,-1],
1084
+ [-1,-1,+1,+1,+1,+1,+1,+1],
1085
+ [-1,+1,-1,+1,+1,+1,-1,-1],
1086
+ [-1,+1,+1,-1,+1,-1,+1,-1],
1087
+ [-1,+1,+1,+1,-1,-1,-1,+1]])
1088
+ res_MapMx[[0,5,6,7,8,9,10,15]] = Mcorr2Map4_re@(res_MMStar.real)
1089
+ res_MapMx[[1,2,3,4,11,12,13,14]] = Mcorr2Map4_im@(res_MMStar.imag)
1090
+ return res_MapMx
1091
+
1092
+
1093
+ class GNNNCorrelation_NoTomo(BinnedNPCF):
1094
+ def __init__(self, min_sep, max_sep, thetabatchsize_max=10000, **kwargs):
1095
+ r""" Class containing methods to measure and and obtain statistics that are built
1096
+ from third-order source-lens-lens (G3L) correlation functions.
1097
+
1098
+ Attributes
1099
+ ----------
1100
+ min_sep: float
1101
+ The smallest distance of each vertex for which the NPCF is computed.
1102
+ max_sep: float
1103
+ The largest distance of each vertex for which the NPCF is computed.
1104
+ nbinsr: int, optional
1105
+ The number of radial bins for each vertex of the NPCF. If set to
1106
+ ``None`` this attribute is inferred from the ``binsize`` attribute.
1107
+ binsize: int, optional
1108
+ The logarithmic slize of the radial bins for each vertex of the NPCF. If set to
1109
+ ``None`` this attribute is inferred from the ``nbinsr`` attribute.
1110
+ nbinsphi: float, optional
1111
+ The number of angular bins for the NPCF in the real-space basis.
1112
+ Defaults to ``100``.
1113
+ nmaxs: list, optional
1114
+ The largest multipole component considered for the NPCF in the multipole basis.
1115
+ Defaults to ``30``.
1116
+ method: str, optional
1117
+ The method to be employed for the estimator. Defaults to ``DoubleTree``.
1118
+ multicountcorr: bool, optional
1119
+ Flag on whether to subtract of multiplets in which the same tracer appears more
1120
+ than once. Defaults to ``True``.
1121
+ shuffle_pix: int, optional
1122
+ Choice of how to define centers of the cells in the spatial hash structure.
1123
+ Defaults to ``1``, i.e. random positioning.
1124
+ tree_resos: list, optional
1125
+ The cell sizes of the hierarchical spatial hash structure
1126
+ tree_edges: list, optional
1127
+ List of radii where the tree changes resolution.
1128
+ rmin_pixsize: int, optional
1129
+ The limiting radial distance relative to the cell of the spatial hash
1130
+ after which one switches to the next hash in the hierarchy. Defaults to ``20``.
1131
+ resoshift_leafs: int, optional
1132
+ Allows for a difference in how the hierarchical spatial hash is traversed for
1133
+ pixels at the base of the NPCF and pixels at leafs. I.e. positive values indicate
1134
+ that leafs will be evaluated at a courser resolutions than the base. Defaults to ``0``.
1135
+ minresoind_leaf: int, optional
1136
+ Sets the smallest resolution in the spatial hash hierarchy which can be used to access
1137
+ tracers at leaf positions. If set to ``None`` uses the smallest specified cell size.
1138
+ Defaults to ``None``.
1139
+ maxresoind_leaf: int, optional
1140
+ Sets the largest resolution in the spatial hash hierarchy which can be used to access
1141
+ tracers at leaf positions. If set to ``None`` uses the largest specified cell size.
1142
+ Defaults to ``None``.
1143
+ nthreads: int, optional
1144
+ The number of openmp threads used for the reduction procedure. Defaults to ``16``.
1145
+ bin_centers: numpy.ndarray
1146
+ The centers of the radial bins for each combination of tomographic redshifts.
1147
+ bin_centers_mean: numpy.ndarray
1148
+ The centers of the radial bins averaged over all combination of tomographic redshifts.
1149
+ phis: list
1150
+ The bin centers for the N-2 angles describing the NPCF
1151
+ in the real-space basis.
1152
+ npcf: numpy.ndarray
1153
+ The natural components of the NPCF in the real space basis. The different axes
1154
+ are specified as follows: ``(component, zcombi, rbin_1, ..., rbin_N-1, phiin_1, phibin_N-2)``.
1155
+ npcf_norm: numpy.ndarray
1156
+ The normalization of the natural components of the NPCF in the real space basis. The different axes
1157
+ are specified as follows: ``(zcombi, rbin_1, ..., rbin_N-1, phiin_1, phibin_N-2)``.
1158
+ npcf_multipoles: numpy.ndarray
1159
+ The natural components of the NPCF in the multipole basis. The different axes
1160
+ are specified as follows: ``(component, zcombi, multipole_1, ..., multipole_N-2, rbin_1, ..., rbin_N-1)``.
1161
+ npcf_multipoles_norm: numpy.ndarray
1162
+ The normalization of the natural components of the NPCF in the multipole basis. The different axes
1163
+ are specified as follows: ``(zcombi, multipole_1, ..., multipole_N-2, rbin_1, ..., rbin_N-1)``.
1164
+ is_edge_corrected: bool, optional
1165
+ Flag signifying on wheter the NPCF multipoles have beed edge-corrected. Defaults to ``False``.
1166
+ """
1167
+ super().__init__(4, [2,0,0,0], n_cfs=1, min_sep=min_sep, max_sep=max_sep, **kwargs)
1168
+ self.nmax = self.nmaxs[0]
1169
+ self.phi = self.phis[0]
1170
+ self.projection = None
1171
+ self.projections_avail = [None, "X"]
1172
+ self.proj_dict = {"X":0}
1173
+ self.nbinsz_source = 1 # This class does not handle any tomography at the moment, so I fix it here
1174
+ self.nbinsz_lens = 1 # This class does not handle any tomography at the moment, so I fix it here
1175
+ self.nzcombis = 1
1176
+ self.thetabatchsize_max = thetabatchsize_max
1177
+
1178
+ # (Add here any newly implemented projections)
1179
+ self._initprojections(self)
1180
+
1181
+ def process(self, cat_source, cat_lens, statistics="all", tofile=False, apply_edge_correction=False,
1182
+ dotomo_source=True, dotomo_lens=True,
1183
+ lowmem=None, apradii=None, batchsize=None, custom_thetacombis=None, cutlen=2**31-1):
1184
+ self._checkcats([cat_source, cat_lens, cat_lens, cat_lens], [2, 0, 0, 0])
1185
+
1186
+ # Checks for redshift binning
1187
+ if not dotomo_source:
1188
+ self.nbinsz_source = 1
1189
+ zbins_source = np.zeros(cat_source.ngal, dtype=np.int32)
1190
+ else:
1191
+ self.nbinsz_source = cat_source.nbinsz
1192
+ zbins_source = cat_source.zbins
1193
+ if not dotomo_lens:
1194
+ self.nbinsz_lens = 1
1195
+ zbins_lens = np.zeros(cat_lens.ngal, dtype=np.int32)
1196
+ else:
1197
+ self.nbinsz_lens = cat_lens.nbinsz
1198
+ zbins_lens= cat_lens.zbins
1199
+
1200
+ ## Preparations ##
1201
+ # Some default argument resettings
1202
+ if self.method=='Discrete' and not lowmem:
1203
+ statistics = ['4pcf_multipole']
1204
+
1205
+ # Check memory requirements
1206
+ if not lowmem:
1207
+ _resradial = gen_thetacombis_fourthorder(nbinsr=self.nbinsr, nthreads=self.nthreads, batchsize=batchsize,
1208
+ batchsize_max=self.thetabatchsize_max, ordered=True, custom=custom_thetacombis,
1209
+ verbose=self._verbose_python*lowmem)
1210
+ nthetacombis_tot, _, _, _, _, _ = _resradial
1211
+ assert(self.nmaxs[0]==self.nmaxs[1])
1212
+ _resmultipoles = gen_n2n3indices_Upsfourth(self.nmaxs[0])
1213
+ _, _inds, _, _ = _resmultipoles
1214
+ ncache_required_out = self.nbinsr*self.nbinsr*self.nbinsr*(2*self.nmaxs[0]+1)*(2*self.nmaxs[1]+1)
1215
+ ncache_required_alloc = nthetacombis_tot*len(_inds)*self.nthreads
1216
+ if max(ncache_required_out,ncache_required_alloc)>2**31-1:
1217
+ raise ValueError("Required memory too large (%.2f / x 10^9 elements)"%(ncache_required_out/1e9,ncache_required_alloc/1e9))
1218
+
1219
+ # Build list of statistics to be calculated
1220
+ statistics_avail_4pcf = ["4pcf_real", "4pcf_multipole"]
1221
+ statistics_avail_mapnap3 = ["MN3", "MapNap3", "MN3cc", "MapNap3c"]
1222
+ statistics_avail_comp = ["allMapNap3", "all4pcf", "all"]
1223
+ statistics_avail_phys = statistics_avail_4pcf + statistics_avail_mapnap3
1224
+ statistics_avail = statistics_avail_4pcf + statistics_avail_mapnap3 + statistics_avail_comp
1225
+ _statistics = []
1226
+ hasintegratedstats = False
1227
+ _strbadstats = lambda stat: ("The statistics `%s` has not been implemented yet. "%stat +
1228
+ "Currently supported statistics are:\n" + str(statistics_avail))
1229
+ if type(statistics) not in [list, str]:
1230
+ raise ValueError("The parameter `statistics` should either be a list or a string.")
1231
+ if type(statistics) is str:
1232
+ if statistics not in statistics_avail:
1233
+ raise ValueError(_strbadstats)
1234
+ statistics = [statistics]
1235
+ if type(statistics) is list:
1236
+ if "all" in statistics:
1237
+ _statistics = statistics_avail_phys
1238
+ elif "all4pcf" in statistics:
1239
+ _statistics.append(statistics_avail_4pcf)
1240
+ elif "allMapNap3" in statistics:
1241
+ _statistics.append(statistics_avail_mapnap3)
1242
+ _statistics = flatlist(_statistics)
1243
+ for stat in statistics:
1244
+ if stat not in statistics_avail:
1245
+ raise ValueError(_strbadstats)
1246
+ if stat in statistics_avail_phys and stat not in _statistics:
1247
+ _statistics.append(stat)
1248
+ statistics = list(set(flatlist(_statistics)))
1249
+ for stat in statistics:
1250
+ if stat in statistics_avail_mapnap3:
1251
+ hasintegratedstats = True
1252
+
1253
+ # Init optional args
1254
+ __lenflag = 10
1255
+ __fillflag = -1
1256
+ _nmax = self.nmaxs[0]
1257
+ _nnvals = (2*_nmax+1)*(2*_nmax+1)
1258
+ _nbinsr3 = self.nbinsr*self.nbinsr*self.nbinsr
1259
+ _nphis = len(self.phis[0])
1260
+ _r2combis = self.nbinsr*self.nbinsr
1261
+ sc = (self.n_cfs, 2*self.nmax+1, 2*self.nmax+1, self.nzcombis, self.nbinsr, self.nbinsr, self.nbinsr)
1262
+ sn = (2*self.nmax+1,2*self.nmax+1,self.nzcombis,self.nbinsr,self.nbinsr,self.nbinsr)
1263
+ szr = (self.nbinsz_source, self.nbinsz_lens, self.nbinsr)
1264
+ s4pcf = (self.n_cfs,self.nzcombis,self.nbinsr,self.nbinsr,self.nbinsr,_nphis,_nphis)
1265
+ s4pcfn = (self.nzcombis,self.nbinsr,self.nbinsr,self.nbinsr,_nphis,_nphis)
1266
+ bin_centers = np.zeros(reduce(operator.mul, szr)).astype(np.float64)
1267
+
1268
+ if "4pcf_multipole" in statistics:
1269
+ Upsilon_n = np.zeros(self.n_cfs*_nnvals*self.nzcombis*_nbinsr3).astype(np.complex128)
1270
+ N_n = np.zeros(_nnvals*self.nzcombis*_nbinsr3).astype(np.complex128)
1271
+ alloc_4pcfmultipoles = 1
1272
+ else:
1273
+ Upsilon_n = __fillflag*np.ones(__lenflag).astype(np.complex128)
1274
+ N_n = __fillflag*np.zeros(__lenflag).astype(np.complex128)
1275
+ alloc_4pcfmultipoles = 0
1276
+ if "4pcf_real" in statistics:
1277
+ fourpcf = np.zeros(1*_nphis*_nphis*self.nzcombis*_nbinsr3).astype(np.complex128)
1278
+ fourpcf_norm = np.zeros(_nphis*_nphis*self.nzcombis*_nbinsr3).astype(np.complex128)
1279
+ alloc_4pcfreal = 1
1280
+ else:
1281
+ fourpcf = __fillflag*np.ones(__lenflag).astype(np.complex128)
1282
+ fourpcf_norm = __fillflag*np.ones(__lenflag).astype(np.complex128)
1283
+ alloc_4pcfreal = 0
1284
+ if hasintegratedstats:
1285
+ if apradii is None:
1286
+ raise ValueError("Aperture radii need to be specified in variable `apradii`.")
1287
+ apradii = apradii.astype(np.float64)
1288
+ MN3correlators = np.zeros(1*self.nzcombis*len(apradii)).astype(np.complex128)
1289
+ else:
1290
+ apradii = __fillflag*np.ones(__lenflag).astype(np.float64)
1291
+ MN3correlators = __fillflag*np.ones(__lenflag).astype(np.complex128)
1292
+
1293
+ # Basic prep
1294
+ hash_dpix = max(1.,self.max_sep//10.)
1295
+ jointextent = list(cat_source._jointextent([cat_lens], extend=self.tree_resos[-1]))
1296
+ cat_source.build_spatialhash(dpix=hash_dpix, extent=jointextent)
1297
+ cat_lens.build_spatialhash(dpix=hash_dpix, extent=jointextent)
1298
+
1299
+ args_sourcecat = (cat_source.isinner.astype(np.float64), cat_source.weight.astype(np.float64),
1300
+ cat_source.pos1.astype(np.float64), cat_source.pos2.astype(np.float64),
1301
+ cat_source.tracer_1.astype(np.float64), cat_source.tracer_2.astype(np.float64),
1302
+ np.int32(cat_source.ngal), )
1303
+ args_basesetup = (np.int32(self.nmax), np.float64(self.min_sep), np.float64(self.max_sep),
1304
+ np.int32(self.nbinsr), np.int32(self.multicountcorr), )
1305
+
1306
+
1307
+ if self.method=="Discrete" and not lowmem:
1308
+ hash_dpix = max(1.,self.max_sep//10.)
1309
+ jointextent = list(cat_source._jointextent([cat_lens], extend=self.tree_resos[-1]))
1310
+ cat_source.build_spatialhash(dpix=hash_dpix, extent=jointextent)
1311
+ cat_lens.build_spatialhash(dpix=hash_dpix, extent=jointextent)
1312
+ nregions = np.int32(len(np.argwhere(cat_lens.index_matcher>-1).flatten()))
1313
+ args_lenscat = (cat_lens.weight.astype(np.float64),
1314
+ cat_lens.pos1.astype(np.float64), cat_lens.pos2.astype(np.float64), np.int32(cat_lens.ngal), )
1315
+ args_hash = (cat_lens.index_matcher, cat_lens.pixs_galind_bounds, cat_lens.pix_gals, nregions, )
1316
+ args_pix = (np.float64(cat_lens.pix1_start), np.float64(cat_lens.pix1_d), np.int32(cat_lens.pix1_n),
1317
+ np.float64(cat_lens.pix2_start), np.float64(cat_lens.pix2_d), np.int32(cat_lens.pix2_n), )
1318
+ args_4pcf = (bin_centers, Upsilon_n, N_n, )
1319
+
1320
+ args = (*args_sourcecat,
1321
+ *args_lenscat,
1322
+ *args_hash,
1323
+ *args_pix,
1324
+ *args_basesetup,
1325
+ np.int32(self.nthreads),
1326
+ *args_4pcf)
1327
+ func = self.clib.alloc_notomoGammans_discrete_gnnn
1328
+
1329
+ if self.method=="Tree":
1330
+ # Prepare mask for nonredundant theta- and multipole configurations
1331
+ _resradial = gen_thetacombis_fourthorder(nbinsr=self.nbinsr, nthreads=self.nthreads, batchsize=batchsize,
1332
+ batchsize_max=self.thetabatchsize_max, ordered=True, custom=custom_thetacombis,
1333
+ verbose=self._verbose_python*lowmem)
1334
+ nthetacombis_tot, _, thetacombis_batches, cumnthetacombis_batches, nthetacombis_batches, nbatches = _resradial
1335
+ assert(self.nmaxs[0]==self.nmaxs[1])
1336
+ _resmultipoles = gen_n2n3indices_Upsfourth(self.nmaxs[0])
1337
+ _shape, _inds, _n2s, _n3s = _resmultipoles
1338
+
1339
+ # Prepare reduced catalogs
1340
+ cutfirst = np.int32(self.tree_resos[0]==0.)
1341
+ mhash = cat_lens.multihash(dpixs=self.tree_resos[cutfirst:], dpix_hash=self.tree_resos[-1],
1342
+ shuffle=self.shuffle_pix, normed=True)
1343
+ (ngal_resos_lens, pos1s_lens, pos2s_lens, weights_lens, _, _, _,
1344
+ index_matchers_lens, pixs_galind_bounds_lens, pix_gals_lens, dpixs1_true_lens, dpixs2_true_lens) = mhash
1345
+ weight_resos_lens = np.concatenate(weights_lens).astype(np.float64)
1346
+ pos1_resos_lens = np.concatenate(pos1s_lens).astype(np.float64)
1347
+ pos2_resos_lens = np.concatenate(pos2s_lens).astype(np.float64)
1348
+ index_matcher_resos_lens = np.concatenate(index_matchers_lens).astype(np.int32)
1349
+ pixs_galind_bounds_resos_lens = np.concatenate(pixs_galind_bounds_lens).astype(np.int32)
1350
+ pix_gals_resos_lens = np.concatenate(pix_gals_lens).astype(np.int32)
1351
+ index_matcher_flat = np.argwhere(cat_source.index_matcher>-1).flatten()
1352
+ nregions = len(index_matcher_flat)
1353
+
1354
+ args_resos = (np.int32(self.tree_nresos), self.tree_redges, )
1355
+ args_resos_lens = (weight_resos_lens, pos1_resos_lens, pos2_resos_lens, np.asarray(ngal_resos_lens).astype(np.int32),)
1356
+ args_hash_source = (cat_source.index_matcher, cat_source.pixs_galind_bounds, cat_source.pix_gals, )
1357
+ args_mhash_lens = (index_matcher_resos_lens, pixs_galind_bounds_resos_lens, pix_gals_resos_lens, np.int32(nregions), )
1358
+ args_hash = (np.float64(cat_lens.pix1_start), np.float64(cat_lens.pix1_d), np.int32(cat_lens.pix1_n),
1359
+ np.float64(cat_lens.pix2_start), np.float64(cat_lens.pix2_d), np.int32(cat_lens.pix2_n), )
1360
+ if lowmem:
1361
+ # Build args
1362
+ args_indsetup = (_inds, np.int32(len(_inds)), self.phis[0].astype(np.float64),
1363
+ 2*np.pi/_nphis*np.ones(_nphis, dtype=np.float64), np.int32(_nphis), )
1364
+ args_thetas = (thetacombis_batches, nthetacombis_batches, cumnthetacombis_batches, nbatches, )
1365
+ args_mapnap3 = (apradii, np.int32(len(apradii)), MN3correlators)
1366
+ args_4pcf = (np.int32(alloc_4pcfmultipoles), np.int32(alloc_4pcfreal),
1367
+ bin_centers, Upsilon_n, N_n, fourpcf, fourpcf_norm, )
1368
+ args = (*args_resos,
1369
+ *args_sourcecat,
1370
+ *args_resos_lens,
1371
+ *args_hash_source,
1372
+ *args_mhash_lens,
1373
+ *args_hash,
1374
+ *args_basesetup,
1375
+ *args_indsetup,
1376
+ *args_thetas,
1377
+ np.int32(self.nthreads),
1378
+ *args_mapnap3,
1379
+ *args_4pcf)
1380
+ func = self.clib.alloc_notomoMapNap3_tree_gnnn
1381
+ else:
1382
+ args_indsetup = (np.int32(nthetacombis_tot), _inds, np.int32(len(_inds)), )
1383
+ args_4pcf = (bin_centers, Upsilon_n, N_n, )
1384
+ args = (*args_resos,
1385
+ *args_sourcecat,
1386
+ *args_resos_lens,
1387
+ *args_hash_source,
1388
+ *args_mhash_lens,
1389
+ *args_hash,
1390
+ *args_basesetup,
1391
+ *args_indsetup,
1392
+ np.int32(self.nthreads),
1393
+ np.int32(self._verbose_c),
1394
+ *args_4pcf)
1395
+ func = self.clib.alloc_notomoGammans_tree_gnnn
1396
+
1397
+ if self._verbose_debug:
1398
+ for elarg, arg in enumerate(args):
1399
+ toprint = (elarg, type(arg),)
1400
+ if isinstance(arg, np.ndarray):
1401
+ toprint += (type(arg[0]), arg.shape)
1402
+ try:
1403
+ toprint += (func.argtypes[elarg], )
1404
+ except:
1405
+ print("Weird error for arg %i."%elarg)
1406
+ print(toprint)
1407
+ print(arg)
1408
+
1409
+ func(*args)
1410
+
1411
+ ## Massage the output ##
1412
+ istatout = ()
1413
+ self.bin_centers = bin_centers.reshape(szr)
1414
+ self.bin_centers_mean = np.mean(self.bin_centers, axis=0)
1415
+ self.projection = "X"
1416
+ self.is_edge_corrected = False
1417
+ if "4pcf_multipole" in statistics:
1418
+ self.npcf_multipoles = Upsilon_n.reshape(sc)
1419
+ self.npcf_multipoles_norm = N_n.reshape(sn)
1420
+ if "4pcf_real" in statistics:
1421
+ if lowmem:
1422
+ self.npcf = fourpcf.reshape(s4pcf)
1423
+ self.npcf_norm = fourpcf_norm.reshape(s4pcfn)
1424
+ else:
1425
+ if self._verbose_python:
1426
+ print("Transforming output to real space basis")
1427
+ self.multipoles2npcf_c()
1428
+ if hasintegratedstats:
1429
+ if "MN3" in statistics:
1430
+ istatout += (MN3correlators.reshape((1,self.nzcombis,len(apradii))), )
1431
+ # TODO allocate map4, map4c etc.
1432
+
1433
+ if apply_edge_correction:
1434
+ self.edge_correction()
1435
+
1436
+ return istatout
1437
+
1438
+ # TODO:
1439
+ # * Include the z-weighting method
1440
+ # * Include the 2pcf as spline --> Should we also add an option to compute it here? Might be a mess
1441
+ # as then we also would need methods to properly distribute randoms...
1442
+ # * Do a voronoi-tesselation at the multipole level? Would be just 2D, but still might help? Eventually
1443
+ # bundle together cells s.t. tot_weight > theshold? However, this might then make the binning courser
1444
+ # for certain triangle configs(?)
1445
+ def multipoles2npcf(self):
1446
+ raise NotImplementedError
1447
+
1448
+ def multipoles2npcf_singlethetcombi(self, elthet1, elthet2, elthet3, projection="X"):
1449
+ r""" Converts a 4PCF in the multipole basis in the real space basis for a fixed combination of radial bins.
1450
+
1451
+ Returns:
1452
+ --------
1453
+ npcf_out: np.ndarray
1454
+ 4PCF components in the real-space bassi for all angular combinations.
1455
+ npcf_norm_out: np.ndarray
1456
+ 4PCF weighted counts in the real-space bassi for all angular combinations.
1457
+ """
1458
+ assert((projection in self.proj_dict.keys()) and (projection in self.projections_avail))
1459
+
1460
+ _phis1 = self.phis[0].astype(np.float64)
1461
+ _phis2 = self.phis[1].astype(np.float64)
1462
+ _nphis1 = len(self.phis[0])
1463
+ _nphis2 = len(self.phis[1])
1464
+ ncfs, nnvals, _, nzcombis, nbinsr, _, _ = np.shape(self.npcf_multipoles)
1465
+
1466
+ Upsilon_in = self.npcf_multipoles[...,elthet1,elthet2,elthet3].flatten()
1467
+ N_in = self.npcf_multipoles_norm[...,elthet1,elthet2,elthet3].flatten()
1468
+ npcf_out = np.zeros(self.n_cfs*nzcombis*_nphis1*_nphis2, dtype=np.complex128)
1469
+ npcf_norm_out = np.zeros(nzcombis*_nphis1*_nphis2, dtype=np.complex128)
1470
+
1471
+ self.clib.multipoles2npcf_gnnn_singletheta(
1472
+ Upsilon_in, N_in, self.nmaxs[0], self.nmaxs[1],
1473
+ self.bin_centers_mean[elthet1], self.bin_centers_mean[elthet2], self.bin_centers_mean[elthet3],
1474
+ _phis1, _phis2, _nphis1, _nphis2,
1475
+ npcf_out, npcf_norm_out)
1476
+
1477
+ return npcf_out.reshape((self.n_cfs, _nphis1,_nphis2)), npcf_norm_out.reshape((_nphis1,_nphis2))
1478
+
1479
+ def multipoles2npcf_singletheta_nconvergence(self, elthet1, elthet2, elthet3):
1480
+ r""" Checks convergence of the conversion between mutltipole-space and real space for a combination of radial bins.
1481
+
1482
+ Returns:
1483
+ --------
1484
+ npcf_out: np.ndarray
1485
+ Natural 4PCF components in the real-space basis for all angular combinations.
1486
+ npcf_norm_out: np.ndarray
1487
+ 4PCF weighted counts in the real-space basis for all angular combinations.
1488
+ """
1489
+
1490
+ _phis1 = self.phis[0].astype(np.float64)
1491
+ _phis2 = self.phis[1].astype(np.float64)
1492
+ _nphis1 = len(self.phis[0])
1493
+ _nphis2 = len(self.phis[1])
1494
+
1495
+ ncfs, nnvals, _, nzcombis, nbinsr, _, _ = np.shape(self.npcf_multipoles)
1496
+
1497
+ Upsilon_in = self.npcf_multipoles[...,elthet1,elthet2,elthet3].flatten()
1498
+ N_in = self.npcf_multipoles_norm[...,elthet1,elthet2,elthet3].flatten()
1499
+ npcf_out = np.zeros(self.n_cfs*nzcombis*(self.nmaxs[0]+1)*(self.nmaxs[1]+1)*_nphis1*_nphis2, dtype=np.complex128)
1500
+ npcf_norm_out = np.zeros(nzcombis*(self.nmaxs[0]+1)*(self.nmaxs[1]+1)*_nphis1*_nphis2, dtype=np.complex128)
1501
+
1502
+ self.clib.multipoles2npcf_gnnn_singletheta_nconvergence(
1503
+ Upsilon_in, N_in, self.nmaxs[0], self.nmaxs[1],
1504
+ self.bin_centers_mean[elthet1], self.bin_centers_mean[elthet2], self.bin_centers_mean[elthet3],
1505
+ _phis1, _phis2, _nphis1, _nphis2,
1506
+ npcf_out, npcf_norm_out)
1507
+
1508
+ npcf_out = npcf_out.reshape((self.n_cfs, self.nmaxs[0]+1, self.nmaxs[1]+1, _nphis1, _nphis2))
1509
+ npcf_norm_out = npcf_norm_out.reshape((self.nmaxs[0]+1, self.nmaxs[1]+1, _nphis1, _nphis2))
1510
+
1511
+ return npcf_out, npcf_norm_out
1512
+
1513
+
1514
+ ## PROJECTIONS ##
1515
+ def projectnpcf(self, projection):
1516
+ super()._projectnpcf(self, projection)
1517
+
1518
+ ## INTEGRATED MEASURES ##
1519
+ def computeMapNap3(self, radii, nmax_trafo=None, basis='MapMx'):
1520
+ r"""Computes the fourth-order aperture statistcs using the polynomial filter of Crittenden 2002."""
1521
+
1522
+ assert(basis in ['MapMx','MM*','both'])
1523
+
1524
+ if nmax_trafo is None:
1525
+ nmax_trafo=self.nmaxs[0]
1526
+
1527
+ # Retrieve all the aperture measures in the MM* basis via the 5D transformation eqns
1528
+ MN3correlators = np.zeros(1*len(radii), dtype=np.complex128)
1529
+ self.clib.fourpcfmultipoles2MN3correlators(
1530
+ np.int32(self.nmaxs[0]), np.int32(nmax_trafo),
1531
+ self.bin_edges, self.bin_centers_mean, np.int32(self.nbinsr),
1532
+ radii.astype(np.float64), np.int32(len(radii)),
1533
+ self.phis[0].astype(np.float64), self.phis[1].astype(np.float64),
1534
+ self.dphis[0].astype(np.float64), self.dphis[1].astype(np.float64),
1535
+ len(self.phis[0]), len(self.phis[1]),
1536
+ np.int32(self.proj_dict[self.projection]), np.int32(self.nthreads),
1537
+ self.npcf_multipoles.flatten(), self.npcf_multipoles_norm.flatten(),
1538
+ MN3correlators)
1539
+ res_MMStar = MN3correlators.reshape((1,len(radii)))
1540
+
1541
+ # Allocate result (here the bases are really equivalent...)
1542
+ res = ()
1543
+ if basis=='MM*' or basis=='both':
1544
+ res += (res_MMStar, )
1545
+ if basis=='MapMx' or basis=='both':
1546
+ res += ( res_MMStar, )
1547
+
1548
+ return res
1549
+
1550
+ def MapNap3_corrections(self, apradii, xi_ng=None, Gtilde_third=None,
1551
+ include_second=True, include_third=True, basis='MapMx'):
1552
+
1553
+ if xi_ng is not None and include_second:
1554
+ # Check consistency
1555
+ pass
1556
+ if xi_ng is None and include_second:
1557
+ # Compute gamma_t via treecorr
1558
+ pass
1559
+
1560
+ if Gtilde_third is not None and include_third:
1561
+ # Check consistency
1562
+ pass
1563
+ if Gtilde_third is None and include_third:
1564
+ # Compute GNN via treecorr
1565
+ pass
1566
+ if xi_ng is None:
1567
+ xi_ng = np.zeros(self.nbinsr, dtype=np.float64)
1568
+ if Gtilde_third is None:
1569
+ Gtilde_third = np.zeros(self.nbinsr*self.nbinsr*self.nbinsphi,dtype=np.complex128)
1570
+
1571
+ # This block is similar to MapNap3_analytic
1572
+ self.nbinsz = 1
1573
+ self.nzcombis = 1
1574
+ _nphis = len(self.phis[0])
1575
+ MN3correlators = np.zeros(self.n_cfs*self.nzcombis*len(apradii)).astype(np.complex128)
1576
+ # Define the radial bin batches
1577
+ args_4pcfsetup = (self.bin_edges, self.bin_centers_mean, np.int32(self.nbinsr),
1578
+ self.phis[0].astype(np.float64),
1579
+ (self.phis[0][1]-self.phis[0][0])*np.ones(_nphis, dtype=np.float64), _nphis, np.int32(self.nmaxs[0]), )
1580
+ args_map4 = (apradii.astype(np.float64), np.int32(len(apradii)), )
1581
+
1582
+ args = (*args_4pcfsetup,
1583
+ np.int32(self.nthreads),
1584
+ *args_map4,
1585
+ xi_ng.astype(np.float64),
1586
+ Gtilde_third.flatten(),
1587
+ np.int32(include_second),
1588
+ np.int32(include_third),
1589
+ MN3correlators)
1590
+ func = self.clib.alloc_notomoMapNap3_corrections
1591
+
1592
+ if self._verbose_debug:
1593
+ for elarg, arg in enumerate(args):
1594
+ toprint = (elarg, type(arg),)
1595
+ if isinstance(arg, np.ndarray):
1596
+ toprint += (type(arg[0]), arg.shape)
1597
+ try:
1598
+ toprint += (func.argtypes[elarg], )
1599
+ print(toprint)
1600
+ print(arg)
1601
+ except:
1602
+ print("We did have a problem for arg %i"%elarg)
1603
+
1604
+ func(*args)
1605
+
1606
+ return MN3correlators.reshape((1,len(apradii)))
1607
+
1608
+ def gauss4pcf_analytic(self, itheta1, itheta2, itheta3, nsubr,
1609
+ xing_arr, xinn_arr, thetamin_xi, thetamax_xi, dtheta_xi):
1610
+
1611
+ gauss_4pcf = np.zeros(self.n_cfs*self.nbinsphi[0]*self.nbinsphi[1],dtype=np.complex128)
1612
+
1613
+ self.clib.gtilde4pcf_analytic_integrated(
1614
+ np.int32(itheta1),
1615
+ np.int32(itheta2),
1616
+ np.int32(itheta3),
1617
+ np.int32(nsubr),
1618
+ self.bin_edges.astype(np.float64),
1619
+ np.int32(self.nbinsr),
1620
+ self.phis[0].astype(np.float64),
1621
+ np.int32(self.nbinsphi[0]),
1622
+ xing_arr.astype(np.float64),
1623
+ xinn_arr.astype(np.float64),
1624
+ np.float64(thetamin_xi),
1625
+ np.float64(thetamax_xi),
1626
+ np.float64(dtheta_xi),
1627
+ gauss_4pcf)
1628
+ return gauss_4pcf.reshape((self.n_cfs, self.nbinsphi[0], self.nbinsphi[1]))
1629
+
1630
+ def gnnn_corrections(self, itheta1, itheta2, itheta3, xi_ng=None, Gtilde_third=None,
1631
+ include_second=True, include_third=True):
1632
+
1633
+ if xi_ng is None:
1634
+ xi_ng = np.zeros(self.nbinsr, dtype=np.float64)
1635
+ if Gtilde_third is None:
1636
+ Gtilde_third = np.zeros(self.nbinsr*self.nbinsr*self.nbinsphi,dtype=np.complex128)
1637
+
1638
+ corrs = np.zeros(self.n_cfs*self.nbinsphi[0]*self.nbinsphi[1],dtype=np.complex128)
1639
+ self.clib.gtilde4pcf_corrections(
1640
+ np.int32(itheta1),
1641
+ np.int32(itheta2),
1642
+ np.int32(itheta3),
1643
+ np.int32(self.nbinsr),
1644
+ self.phis[0].astype(np.float64),
1645
+ np.int32(self.nbinsphi[0]),
1646
+ np.int32(self.nmaxs[0]),
1647
+ np.int32(include_second),
1648
+ np.int32(include_third),
1649
+ xi_ng.astype(np.float64),
1650
+ Gtilde_third.flatten().astype(np.complex128),
1651
+ corrs)
1652
+
1653
+ return corrs.reshape((self.n_cfs, self.nbinsphi[0], self.nbinsphi[1]))
1654
+
1655
+ # Disconnected part of MapNap^3 from analytic 2pcfs
1656
+ # thetamin_xi, thetamax_xi, ntheta_xi is the linspaced array in which the xipm are passed to the external function
1657
+ def MapNap3analytic(self, mapradii, xing_spl, xinn_spl, thetamin_xi, thetamax_xi, ntheta_xi,
1658
+ nsubr=1, nsubsample_filter=1, batchsize=None, basis='MapMx'):
1659
+
1660
+ self.nbinsz = 1
1661
+ self.nzcombis = 1
1662
+ _nmax = self.nmaxs[0]
1663
+ _nnvals = (2*_nmax+1)*(2*_nmax+1)
1664
+ _nbinsr3 = self.nbinsr*self.nbinsr*self.nbinsr
1665
+ _nphis = len(self.phis[0])
1666
+ bin_centers = np.zeros(self.nbinsz*self.nbinsr).astype(np.float64)
1667
+ MN3correlators = np.zeros(self.n_cfs*self.nzcombis*len(mapradii)).astype(np.complex128)
1668
+ # Define the radial bin batches
1669
+ if batchsize is None:
1670
+ batchsize = min(_nbinsr3,min(10000,int(_nbinsr3/self.nthreads)))
1671
+ if self._verbose_python:
1672
+ print("Using batchsize of %i for radial bins"%batchsize)
1673
+ nbatches = np.int32(_nbinsr3/batchsize)
1674
+ thetacombis_batches = np.arange(_nbinsr3).astype(np.int32)
1675
+ cumnthetacombis_batches = (np.arange(nbatches+1)*_nbinsr3/(nbatches)).astype(np.int32)
1676
+ nthetacombis_batches = (cumnthetacombis_batches[1:]-cumnthetacombis_batches[:-1]).astype(np.int32)
1677
+ cumnthetacombis_batches[-1] = _nbinsr3
1678
+ nthetacombis_batches[-1] = _nbinsr3-cumnthetacombis_batches[-2]
1679
+ thetacombis_batches = thetacombis_batches.flatten().astype(np.int32)
1680
+ nbatches = len(nthetacombis_batches)
1681
+
1682
+ args_4pcfsetup = (np.float64(self.min_sep), np.float64(self.max_sep), np.int32(self.nbinsr),
1683
+ self.phis[0].astype(np.float64),
1684
+ (self.phis[0][1]-self.phis[0][0])*np.ones(_nphis, dtype=np.float64), _nphis, np.int32(nsubr), )
1685
+ args_thetas = (thetacombis_batches, nthetacombis_batches, cumnthetacombis_batches, nbatches, )
1686
+ args_map4 = (mapradii.astype(np.float64), np.int32(len(mapradii)), )
1687
+ thetas_xi = np.linspace(thetamin_xi,thetamax_xi,ntheta_xi+1)
1688
+ args_xi = (xing_spl(thetas_xi), xinn_spl(thetas_xi), thetamin_xi, thetamax_xi, ntheta_xi, nsubsample_filter, )
1689
+ args = (*args_4pcfsetup,
1690
+ *args_thetas,
1691
+ np.int32(self.nthreads),
1692
+ *args_map4,
1693
+ *args_xi,
1694
+ MN3correlators)
1695
+ func = self.clib.alloc_notomoMapNap3_analytic
1696
+
1697
+ if self._verbose_debug:
1698
+ for elarg, arg in enumerate(args):
1699
+ toprint = (elarg, type(arg),)
1700
+ if isinstance(arg, np.ndarray):
1701
+ toprint += (type(arg[0]), arg.shape)
1702
+ try:
1703
+ toprint += (func.argtypes[elarg], )
1704
+ print(toprint)
1705
+ print(arg)
1706
+ except:
1707
+ print("We did have a problem for arg %i"%elarg)
1708
+
1709
+ func(*args)
1710
+
1711
+ res_MMStar = MN3correlators.reshape((self.n_cfs,len(mapradii)))
1712
+ # Allocate result
1713
+ res = ()
1714
+ res += (res_MMStar, )
1715
+
1716
+ return res