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/direct.py ADDED
@@ -0,0 +1,1091 @@
1
+ import ctypes as ct
2
+ from copy import deepcopy
3
+ import glob
4
+ from math import factorial
5
+ import numpy as np
6
+ from numpy.ctypeslib import ndpointer
7
+ import operator
8
+ from pathlib import Path
9
+ import sys
10
+ from .flat2dgrid import FlatPixelGrid_2D, FlatDataGrid_2D
11
+ from .catalog import Catalog, ScalarTracerCatalog, SpinTracerCatalog
12
+
13
+ __all__ = ["DirectEstimator", "Direct_MapnEqual", "Direct_NapnEqual", "MapCombinatorics"]
14
+
15
+ class DirectEstimator:
16
+ r"""
17
+ Class of aperture statistics up to nth order for various arbitrary tracer catalogs.
18
+ This class contains attributes and methods that can be used across any of its children.
19
+
20
+ Attributes
21
+ ----------
22
+ Rmin : float
23
+ The smallest aperture radius for which the cumulants are computed.
24
+
25
+ Rmax : float
26
+ The largest aperture radius for which the cumulants are computed.
27
+
28
+ nbinsr : int, optional
29
+ The number of radial bins for the aperture radii. If set to
30
+ ``None`` this attribute is inferred from the ``binsize`` attribute.
31
+
32
+ binsize : int, optional
33
+ The logarithmic size of the radial bins for the aperture radii. If set to
34
+ ``None`` this attribute is inferred from the ``nbinsr`` attribute.
35
+
36
+ aperture_centers : str, optional
37
+ How to sample the apertures. Can be ``'grid'`` or ``'density'``.
38
+
39
+ accuracies : int or numpy.ndarray, optional
40
+ The sampling density of aperture centers.
41
+
42
+ * If ``aperture_centers`` is set to ``'grid'``, setting ``accuracy == x``
43
+ places the apertures on a regular grid with pixel size ``R_ap / x``.
44
+ * If ``aperture_centers`` is set to ``'density'``, randomly selects as many
45
+ galaxies as there would be aperture centers on the regular grid.
46
+
47
+ frac_covs : numpy.ndarray, optional
48
+ The different aperture coverage bins for which the statistics are evaluated. The first bin
49
+ only includes apertures with ``coverage <= frac_covs[0]`` while the other coverage bins include the
50
+ intervals between ``frac_covs[i]`` and ``frac_covs[i+1]``. Coverage is defined as the percentage of
51
+ the aperture area that is not within the survey area.
52
+
53
+ dpix_hash : float, optional
54
+ The pixel size of the spatial hash used to search through the catalog.
55
+
56
+ weight_outer : float, optional
57
+ The fractional weight applied to galaxies not contained within the interior of the catalog.
58
+ This only affects catalogs which are overlapping patches of a full-sky catalog.
59
+
60
+ weight_inpainted : float, optional
61
+ The fractional weight applied to virtual galaxies inpainted into the catalog. This only
62
+ affects catalogs which have objects in them that are labeled as inpainted.
63
+
64
+ method : str, optional
65
+ The method to be employed for the estimator. Defaults to ``Discrete``.
66
+
67
+ multicountcorr : bool, optional
68
+ Flag on whether to subtract multiplets in which the same tracer appears more
69
+ than once. Defaults to ``True``.
70
+
71
+ shuffle_pix : int, optional
72
+ Choice of how to define centers of the cells in the spatial hash structure.
73
+ Defaults to ``1``, i.e. random positioning.
74
+
75
+ tree_resos : list, optional
76
+ The cell sizes of the hierarchical spatial hash structure.
77
+
78
+ tree_redges : list, optional
79
+ Deprecated (possibly).
80
+
81
+ rmin_pixsize : int, optional
82
+ The limiting radial distance relative to the cell of the spatial hash
83
+ after which one switches to the next hash in the hierarchy. At the moment
84
+ does have no effect.Defaults to ``20``.
85
+
86
+ resoshift_leafs : int, optional
87
+ Allows for a difference in how the hierarchical spatial hash is traversed for
88
+ pixels at the base of the NPCF and pixels at leafs. Positive values indicate
89
+ that leafs will be evaluated at coarser resolutions than the base. At the moment
90
+ does have no effect. Defaults to ``0``.
91
+
92
+ minresoind_leaf : int, optional
93
+ Sets the smallest resolution in the spatial hash hierarchy which can be used to access
94
+ tracers at leaf positions. If set to ``None`` uses the smallest specified cell size.
95
+ At the moment does have no effect. Defaults to ``None``.
96
+
97
+
98
+ maxresoind_leaf : int, optional
99
+ Sets the largest resolution in the spatial hash hierarchy which can be used to access
100
+ tracers at leaf positions. If set to ``None`` uses the largest specified cell size.
101
+ At the moment does have no effect. Defaults to ``None``.
102
+
103
+ nthreads : int, optional
104
+ The number of OpenMP threads used for the reduction procedure. Defaults to ``16``.
105
+ """
106
+
107
+ def __init__(self, Rmin, Rmax, nbinsr=None, binsize=None,
108
+ aperture_centers="grid", accuracies=2.,
109
+ frac_covs=[0.,0.1,0.3,0.5,1.], dpix_hash=1.,
110
+ weight_outer=1., weight_inpainted=0.,
111
+ method="Discrete", multicountcorr=True, shuffle_pix=1,
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,nthreads=16):
114
+
115
+ self.Rmin = Rmin
116
+ self.Rmax = Rmax
117
+ self.method = method
118
+ self.multicountcorr = int(multicountcorr)
119
+ self.shuffle_pix = shuffle_pix
120
+ self.aperture_centers = aperture_centers
121
+ self.accuracies = accuracies
122
+ self.frac_covs = np.asarray(frac_covs, dtype=np.float64)
123
+ self.nfrac_covs = len(self.frac_covs)
124
+ self.dpix_hash = dpix_hash
125
+ self.weight_outer = weight_outer
126
+ self.weight_inpainted = weight_inpainted
127
+ self.methods_avail = ["Discrete", "Tree", "BaseTree", "DoubleTree", "FFT"]
128
+ self.tree_resos = np.asarray(tree_resos, dtype=np.float64)
129
+ self.tree_nresos = int(len(self.tree_resos))
130
+ self.tree_redges = tree_redges
131
+ self.rmin_pixsize = rmin_pixsize
132
+ self.resoshift_leafs = resoshift_leafs
133
+ self.minresoind_leaf = minresoind_leaf
134
+ self.maxresoind_leaf = maxresoind_leaf
135
+ self.nthreads = np.int32(max(1,nthreads))
136
+
137
+ self.combinatorics = None # Here we will store a dict to match tomobin indices for arbitrary orders
138
+
139
+ # Check types or arguments
140
+ assert(isinstance(self.Rmin, float))
141
+ assert(isinstance(self.Rmax, float))
142
+ assert(self.aperture_centers in ["grid","density"])
143
+ assert((self.weight_outer>=0.) and (self.weight_outer<=1.))
144
+ assert((self.weight_inpainted>=0.) and (self.weight_inpainted<=1.))
145
+ assert(self.method in self.methods_avail)
146
+ assert(isinstance(self.tree_resos, np.ndarray))
147
+ assert(isinstance(self.tree_resos[0], np.float64))
148
+
149
+ # Setup radial bins
150
+ # Note that we always have self.binsize <= binsize
151
+ assert((binsize!=None) or (nbinsr!=None))
152
+ if nbinsr != None:
153
+ self.nbinsr = int(nbinsr)
154
+ if binsize != None:
155
+ assert(isinstance(binsize, float))
156
+ self.nbinsr = int(np.ceil(np.log(self.Rmax/self.Rmin)/binsize))
157
+ assert(isinstance(self.nbinsr, int))
158
+ self.radii = np.geomspace(self.Rmin, self.Rmax, self.nbinsr)
159
+ # Setup variable for tree estimator
160
+ if self.tree_redges != None:
161
+ assert(isinstance(self.tree_redges, np.ndarray))
162
+ self.tree_redges = self.tree_redges.astype(np.float64)
163
+ assert(len(self.tree_redges)==self.tree_resos+1)
164
+ self.tree_redges = np.sort(self.tree_redges)
165
+ assert(self.tree_redges[0]==self.Rmin)
166
+ assert(self.tree_redges[-1]==self.Rmax)
167
+ else:
168
+ self.tree_redges = np.zeros(len(self.tree_resos)+1)
169
+ self.tree_redges[-1] = self.Rmin
170
+ for elreso, reso in enumerate(self.tree_resos):
171
+ self.tree_redges[elreso] = (reso==0.)*self.Rmax + (reso!=0.)*self.rmin_pixsize*reso
172
+ # Setup accuracies
173
+ if (isinstance(self.accuracies, int) or isinstance(self.accuracies, float)):
174
+ self.accuracies = self.accuracies*np.ones(self.nbinsr,dtype=np.float64)
175
+ self.accuracies = np.asarray(self.accuracies,dtype=np.float64)
176
+ assert(isinstance(self.accuracies,np.ndarray))
177
+ # Prepare leaf resolutions
178
+ if np.abs(self.resoshift_leafs)>=self.tree_nresos:
179
+ self.resoshift_leafs = np.int32((self.tree_nresos-1) * np.sign(self.resoshift_leafs))
180
+ print("Error: Parameter resoshift_leafs is out of bounds. Set to %i."%self.resoshift_leafs)
181
+ if self.minresoind_leaf is None:
182
+ self.minresoind_leaf=0
183
+ if self.maxresoind_leaf is None:
184
+ self.maxresoind_leaf=self.tree_nresos-1
185
+ if self.minresoind_leaf<0:
186
+ self.minresoind_leaf = 0
187
+ print("Error: Parameter minreso_leaf is out of bounds. Set to 0.")
188
+ if self.minresoind_leaf>=self.tree_nresos:
189
+ self.minresoind_leaf = self.tree_nresos-1
190
+ print("Error: Parameter minreso_leaf is out of bounds. Set to %i."%self.minresoint_leaf)
191
+ if self.maxresoind_leaf<0:
192
+ self.maxresoind_leaf = 0
193
+ print("Error: Parameter minreso_leaf is out of bounds. Set to 0.")
194
+ if self.maxresoind_leaf>=self.tree_nresos:
195
+ self.maxresoind_leaf = self.tree_nresos-1
196
+ print("Error: Parameter minreso_leaf is out of bounds. Set to %i."%self.maxresoint_leaf)
197
+ if self.maxresoind_leaf<self.minresoind_leaf:
198
+ print("Error: Parameter maxreso_leaf is smaller than minreso_leaf. Set to %i."%self.minreso_leaf)
199
+
200
+ #############################
201
+ ## Link compiled libraries ##
202
+ #############################
203
+ # Method that works for LP
204
+ target_path = __import__('orpheus').__file__
205
+ self.library_path = str(Path(__import__('orpheus').__file__).parent.absolute())
206
+ self.clib = ct.CDLL(glob.glob(self.library_path+"/orpheus_clib*.so")[0])
207
+
208
+ # In case the environment is weird, compile code manually and load it here...
209
+ #self.clib = ct.CDLL("/vol/euclidraid4/data/lporth/HigherOrderLensing/Estimator/orpheus/orpheus/src/discrete.so")
210
+
211
+ # Method that works for RR (but not for LP with a local HPC install)
212
+ #self.clib = ct.CDLL(search_file_in_site_package(get_site_packages_dir(),"orpheus_clib"))
213
+ #self.library_path = str(Path(__import__('orpheus').__file__).parent.parent.absolute())
214
+ #print(self.library_path)
215
+ #print(self.clib)
216
+ p_c128 = ndpointer(complex, flags="C_CONTIGUOUS")
217
+ p_f64 = ndpointer(np.float64, flags="C_CONTIGUOUS")
218
+ p_f32 = ndpointer(np.float32, flags="C_CONTIGUOUS")
219
+ p_i32 = ndpointer(np.int32, flags="C_CONTIGUOUS")
220
+ p_f64_nof = ndpointer(np.float64)
221
+
222
+ def get_pixelization(self, cat, R_ap, accuracy, R_crop=None, mgrid=True):
223
+ """ Computes pixel grid on inner region of survey field.
224
+
225
+ Arguments:
226
+ ----------
227
+ R_ap (float):
228
+ The radius of the aperture in pixel scale.
229
+ accuracy (float):
230
+ Accuracy parameter for the pixel grid.
231
+ A value of 0.5 results in a grid in which the apertures
232
+ are only touching each other - hence minimizing correlations.
233
+
234
+ Returns:
235
+ --------
236
+ grid_x (array of floats):
237
+ The grid cell centers for the x-coordinate.
238
+ grid_y (array of floats):
239
+ The grid cell centers for the y-coordinate.
240
+
241
+ Notes:
242
+ ------
243
+ The grid covers the rectangel between the extremal x/y coordinates of
244
+ the galaxy catalogue.
245
+ """
246
+
247
+ if float(accuracy)==-1.:
248
+ centers_1 = cat.pos1[cat.isinner>=0.5]
249
+ centers_2 = cat.pos2[cat.isinner>=0.5]
250
+
251
+ else:
252
+ start1 = cat.min1
253
+ start2 = cat.min2
254
+ end1 = cat.max1
255
+ end2 = cat.max2
256
+ if R_crop is not None:
257
+ start1 += R_crop
258
+ start2 += R_crop
259
+ end1 -= R_crop
260
+ end2 -= R_crop
261
+
262
+ len_1 = end1 - start1
263
+ len_2 = end2 - start2
264
+
265
+ npixels_1 = int(np.ceil(accuracy * len_1 / R_ap))
266
+ npixels_2 = int(np.ceil(accuracy * len_2 / R_ap))
267
+
268
+ stepsize_1 = len_1 / npixels_1
269
+ stepsize_2 = len_2 / npixels_2
270
+
271
+ _centers_1 = [start1 + npixel *
272
+ stepsize_1 for npixel in range(npixels_1 + 1)]
273
+ _centers_2 = [start2 + npixel *
274
+ stepsize_2 for npixel in range(npixels_2 + 1)]
275
+
276
+ if mgrid:
277
+ centers_1, centers_2 = np.meshgrid(_centers_1,_centers_2)
278
+ centers_1 = centers_1.flatten()
279
+ centers_2 = centers_2.flatten()
280
+ else:
281
+ centers_1 = np.asarray(_centers_1, dtype=np.float64)
282
+ centers_2 = np.asarray(_centers_2, dtype=np.float64)
283
+
284
+ return centers_1, centers_2
285
+
286
+ def __getmap(self, R, cat, dotomo, field, filter_form):
287
+ """ This simply computes an aperture mass map together with weights and coverages """
288
+
289
+
290
+
291
+ class Direct_MapnEqual(DirectEstimator):
292
+ r"""
293
+ Compute direct estimator for equal-scale aperture mass statistics.
294
+
295
+ Attributes
296
+ ----------
297
+ order_max : int
298
+ Maximum order of the statistics to be computed.
299
+
300
+ Rmin : float
301
+ Minimum aperture radius.
302
+
303
+ Rmax : float
304
+ Maximum aperture radius.
305
+
306
+ field : str, optional
307
+ Type of input field (``"scalar"`` or ``"polar"``).
308
+
309
+ filter_form : str, optional
310
+ Filter type used in the aperture function (``"S98"``, ``"C02"``, ``"Sch04"``, etc.).
311
+
312
+ ap_weights : str, optional
313
+ Aperture weighting strategy (``"Identity"``, ``"InvShot"``).
314
+
315
+ **kwargs : dict
316
+ Additional keyword arguments passed to :class:`DirectEstimator`.
317
+
318
+ Notes
319
+ -----
320
+ Inherits all other parameters and attributes from :class:`DirectEstimator`.
321
+ Additional child-specific parameters can be passed via ``kwargs``.
322
+ """
323
+
324
+ def __init__(self, order_max, Rmin, Rmax, field="polar", filter_form="C02", ap_weights="InvShot", **kwargs):
325
+
326
+ super().__init__(Rmin=Rmin, Rmax=Rmax, **kwargs)
327
+ self.order_max = order_max
328
+ self.nbinsz = None
329
+ self.field = field
330
+ self.filter_form = filter_form
331
+ self.ap_weights = ap_weights
332
+
333
+ self.fields_avail = ["scalar", "polar"]
334
+ self.ap_weights_dict = {"Identity":0, "InvShot":1}
335
+ self.filters_dict = {"S98":0, "C02":1, "Sch04":2, "PolyExp":3}
336
+ self.ap_weights_avail = list(self.ap_weights_dict.keys())
337
+ self.filters_avail = list(self.filters_dict.keys())
338
+ assert(self.field in self.fields_avail)
339
+ assert(self.ap_weights in self.ap_weights_avail)
340
+ assert(self.filter_form in self.filters_avail)
341
+
342
+ # We do not need DoubleTree for equal-aperture estimator
343
+ if self.method=="DoubleTree":
344
+ self.method="Tree"
345
+
346
+ p_c128 = ndpointer(complex, flags="C_CONTIGUOUS")
347
+ p_f64 = ndpointer(np.float64, flags="C_CONTIGUOUS")
348
+ p_f32 = ndpointer(np.float32, flags="C_CONTIGUOUS")
349
+ p_i32 = ndpointer(np.int32, flags="C_CONTIGUOUS")
350
+ p_f64_nof = ndpointer(np.float64)
351
+
352
+ # Compute nth order equal-scale statistics using discrete estimator (E-Mode only!)
353
+ self.clib.MapnSingleEonlyDisc.restype = ct.c_void_p
354
+ self.clib.MapnSingleEonlyDisc.argtypes = [
355
+ ct.c_double, p_f64, p_f64, ct.c_int32,
356
+ ct.c_int32, ct.c_int32, ct.c_int32, ct.c_int32, ct.c_double, ct.c_double,
357
+ p_f64, p_f64, p_f64, p_f64, p_c128, p_i32, ct.c_int32, ct.c_int32,
358
+ p_f64, p_f64, ct.c_int32, ct.c_int32,
359
+ ct.c_double, ct.c_double, ct.c_double, ct.c_double, ct.c_int32, ct.c_int32,
360
+ p_i32, p_i32, p_i32,
361
+ ct.c_int32, p_f64, p_f64]
362
+ self.clib.singleAp_MapnSingleEonlyDisc.restype = ct.c_void_p
363
+ self.clib.singleAp_MapnSingleEonlyDisc.argtypes = [
364
+ ct.c_double, ct.c_double, ct.c_double,
365
+ ct.c_int32, ct.c_int32, ct.c_double, ct.c_double,
366
+ p_f64, p_f64, p_f64, p_f64, p_c128, p_i32, ct.c_int32, ct.c_int32,
367
+ p_f64,
368
+ ct.c_double, ct.c_double, ct.c_double, ct.c_double, ct.c_int32, ct.c_int32,
369
+ p_i32, p_i32, p_i32,
370
+ p_f64, p_f64, p_f64, p_f64, p_f64, p_f64]
371
+
372
+ # Compute aperture mass map for equal-scale stats
373
+ self.clib.ApertureMassMap_Equal.restype = ct.c_void_p
374
+ self.clib.ApertureMassMap_Equal.argtypes = [
375
+ ct.c_double, p_f64, p_f64, ct.c_int32, ct.c_int32,
376
+ ct.c_int32, ct.c_int32, ct.c_int32, ct.c_double, ct.c_double,
377
+ p_f64, p_f64, p_f64, p_f64, p_c128, p_i32, ct.c_int32, ct.c_int32,
378
+ p_f64,
379
+ ct.c_double, ct.c_double, ct.c_double, ct.c_double, ct.c_int32, ct.c_int32,
380
+ p_i32, p_i32, p_i32,
381
+ ct.c_int32, p_f64, p_f64, p_f64, p_f64, p_f64, p_f64]
382
+
383
+
384
+ def process(self, cat, dotomo=True, Emodeonly=True, connected=True, dpix_innergrid=2.):
385
+ r"""
386
+ Computes aperture statistics on a catalog.
387
+
388
+ Parameters
389
+ ----------
390
+ cat : orpheus.SpinTracerCatalog
391
+ The catalog instance to be processed.
392
+ dotomo : bool, optional
393
+ Whether to compute the statistics for all tomographic bin combinations.
394
+ Default is True.
395
+ Emodeonly : bool, optional
396
+ Currently does not have an impact.
397
+ Default is False.
398
+ connected : bool, optional
399
+ Whether to output only the connected part of the aperture mass statistics.
400
+ Does not have an impact at the moment.
401
+ Default is True.
402
+ dpix_innergrid : float, optional
403
+ Pixel size for a rough reconstruction of the angular mask. Used to preselect
404
+ aperture centers in the interior of the survey.
405
+ Default is 2.
406
+
407
+ Returns
408
+ -------
409
+ None
410
+ Currently does not return any value.
411
+ """
412
+
413
+ nbinsz = cat.nbinsz
414
+ if not dotomo:
415
+ nbinsz = 1
416
+ self.nbinsz = nbinsz
417
+
418
+ nzcombis = self._nzcombis_tot(nbinsz,dotomo)
419
+ result_Mapn = np.zeros((self.nbinsr, self.nfrac_covs, nzcombis), dtype=np.float64)
420
+ result_wMapn = np.zeros((self.nbinsr, self.nfrac_covs, nzcombis), dtype=np.float64)
421
+ if (self.method in ["Discrete", "BaseTree"]) and Emodeonly:
422
+ func = self.clib.MapnSingleEonlyDisc
423
+ elif (self.method in ["Discrete", "BaseTree"]) and not Emodeonly:
424
+ raise NotImplementedError
425
+ else:
426
+ raise NotImplementedError
427
+
428
+ # Build a grid that only covers inner part of patch
429
+ # This will be used to preselelct aperture centers
430
+ args_innergrid = cat.togrid(fields=[cat.isinner], dpix=dpix_innergrid, method="NGP", normed=True, tomo=False)
431
+
432
+ for elr, R in enumerate(self.radii):
433
+ nextmap_out = np.zeros(nzcombis*self.nfrac_covs ,dtype=np.float64)
434
+ nextwmap_out = np.zeros(nzcombis*self.nfrac_covs ,dtype=np.float64)
435
+ args = self._buildargs(cat, args_innergrid, dotomo, elr, forfunc="Equal")
436
+ func(*args)
437
+ result_Mapn[elr] = args[-2].reshape((self.nfrac_covs, nzcombis))[:]
438
+ result_wMapn[elr] = args[-1].reshape((self.nfrac_covs, nzcombis))[:]
439
+
440
+ sys.stdout.write("\rDone %i/%i aperture radii"%(elr+1,self.nbinsr))
441
+
442
+ return result_Mapn, result_wMapn
443
+
444
+ def _getindex(self, order, mode, zcombi):
445
+ pass
446
+
447
+ def _buildargs(self, cat, args_innergrid, dotomo, indR, forfunc="Equal"):
448
+
449
+ assert(forfunc in ["Equal", "EqualGrid"])
450
+
451
+ if not dotomo:
452
+ nbinsz = 1
453
+ zbins = np.zeros(cat.ngal, dtype=np.int32)
454
+ else:
455
+ nbinsz = cat.nbinsz
456
+ zbins = cat.zbins.astype(np.int32)
457
+
458
+ # Initialize combinatorics instances for lateron
459
+ # TODO: Should find a better place where to put this...
460
+ self.combinatorics = {}
461
+ for order in range(1,self.order_max+1):
462
+ self.combinatorics[order] = MapCombinatorics(nbinsz,order_max=order)
463
+
464
+ # Parameters related to the aperture grid
465
+ if forfunc=="Equal":
466
+ # Get centers and check that they are in the interior of the inner catalog
467
+ centers_1, centers_2 = self.get_pixelization(cat, self.radii[indR], self.accuracies[indR], R_crop=0., mgrid=True)
468
+ _f, _s1, _s2, _dpixi, _, _, = args_innergrid
469
+ #pixs_c = (((centers_1-_s1)//_dpixi)*_f[0,1].shape[0] + (centers_2-_s2)//_dpixi).astype(int)
470
+ pixs_c = (((centers_2-_s2)//_dpixi)*_f[0,1].shape[1] + (centers_1-_s1)//_dpixi).astype(int)
471
+
472
+ sel_inner = _f[0,1]>0.
473
+ sel_centers = sel_inner.flatten()[pixs_c]
474
+ # For regular grid, select aperture centers within the interior of the survey
475
+ if self.aperture_centers=="grid":
476
+ centers_1 = centers_1[sel_centers]
477
+ centers_2 = centers_2[sel_centers]
478
+ ncenters = len(centers_1)
479
+ # For density-based grid, select fraction of galaxy positions as aperture centers
480
+ # Note that this will not bias the result as the galaxy residing at the center is
481
+ # not taken into account in the directestimator.c code.
482
+ elif self.aperture_centers=="density":
483
+ rng = np.random.RandomState(1234567890)
484
+ ncenters = max(len(centers_1),cat.ngal)
485
+ sel_centers = rng.random.choice(np.arange(cat.ngal), ncenters, replace=False).astype(np.int32)
486
+ centers_1 = cat.pos1[sel_centers]
487
+ centers_2 = cat.pos2[sel_centers]
488
+ elif forfunc=="EqualGrid":
489
+ # Get centers along each dimension
490
+ centers_1, centers_2 = self.get_pixelization(cat, self.radii[indR], self.accuracies[indR], R_crop=0., mgrid=False)
491
+ ncenters = len(centers_1)*len(centers_2)
492
+
493
+ cat.build_spatialhash(dpix=self.dpix_hash, extent=[None, None, None, None])
494
+ hashgrid = FlatPixelGrid_2D(cat.pix1_start, cat.pix2_start,
495
+ cat.pix1_n, cat.pix2_n, cat.pix1_d, cat.pix2_d)
496
+ regridded_mask = cat.mask.regrid(hashgrid).data.flatten().astype(np.float64)
497
+
498
+ len_out = self._nzcombis_tot(nbinsz,dotomo)*self.nfrac_covs
499
+
500
+ if forfunc=="Equal":
501
+ args_centers = (self.radii[indR], centers_1, centers_2, ncenters,)
502
+ elif forfunc=="EqualGrid":
503
+ args_centers = (self.radii[indR], centers_1, centers_2, len(centers_1), len(centers_2), )
504
+ if forfunc=="Equal":
505
+ args_ofw = (self.order_max, self.filters_dict[self.filter_form], self.ap_weights_dict[self.ap_weights],
506
+ np.int32(self.multicountcorr), np.float64(self.weight_outer), np.float64(self.weight_inpainted), )
507
+ elif forfunc=="EqualGrid":
508
+ args_ofw = (self.order_max, self.filters_dict[self.filter_form], self.ap_weights_dict[self.ap_weights],
509
+ np.float64(self.weight_outer), np.float64(self.weight_inpainted), )
510
+ args_cat = (cat.weight.astype(np.float64), cat.isinner.astype(np.float64),
511
+ cat.pos1.astype(np.float64), cat.pos2.astype(np.float64),
512
+ cat.tracer_1.astype(np.float64)+1j*cat.tracer_2.astype(np.float64),
513
+ zbins, np.int32(nbinsz), np.int32(cat.ngal), )
514
+ if forfunc=="Equal":
515
+ args_mask = (regridded_mask, self.frac_covs, self.nfrac_covs, 1, )
516
+ elif forfunc=="EqualGrid":
517
+ args_mask = (regridded_mask, )
518
+ args_hash = (np.float64(cat.pix1_start), np.float64(cat.pix2_start),
519
+ np.float64(cat.pix1_d), np.float64(cat.pix2_d),
520
+ np.int32(cat.pix1_n), np.int32(cat.pix2_n),
521
+ cat.index_matcher.astype(np.int32), cat.pixs_galind_bounds.astype(np.int32),
522
+ cat.pix_gals.astype(np.int32)
523
+ )
524
+ if forfunc=="Equal":
525
+ args_out = (np.zeros(len_out).astype(np.float64), np.zeros(len_out).astype(np.float64))
526
+ elif forfunc=="EqualGrid":
527
+ args_out = (np.zeros(3*nbinsz*ncenters).astype(np.float64),
528
+ np.zeros(2*ncenters).astype(np.float64),
529
+ np.zeros(self.order_max*nbinsz*ncenters).astype(np.float64),
530
+ np.zeros(self.order_max*nbinsz*ncenters).astype(np.float64),
531
+ np.zeros(self.order_max*nbinsz*ncenters).astype(np.float64),
532
+ np.zeros(self.order_max*nbinsz*ncenters).astype(np.float64), )
533
+ # Return the parameters for the Map computation
534
+ args = (*args_centers,
535
+ *args_ofw,
536
+ *args_cat,
537
+ *args_mask,
538
+ *args_hash,
539
+ np.int32(self.nthreads),
540
+ *args_out)
541
+ if False:
542
+ for elarg, arg in enumerate(args):
543
+ func = self.clib.MapnSingleEonlyDisc
544
+ toprint = (elarg, type(arg),)
545
+ if isinstance(arg, np.ndarray):
546
+ toprint += (type(arg[0]), arg.shape)
547
+ try:
548
+ toprint += (func.argtypes[elarg], )
549
+ print(toprint)
550
+ print(arg)
551
+ except:
552
+ print("We did have a problem for arg %i"%elarg)
553
+
554
+ return args
555
+
556
+ def _nzcombis_tot(self, nbinsz, dotomo):
557
+ res = 0
558
+ for order in range(1, self.order_max+1):
559
+ res += self._nzcombis_order(order, nbinsz, dotomo)
560
+ return res
561
+
562
+ def _nzcombis_order(self, order, nbinsz, dotomo):
563
+ if not dotomo:
564
+ return 1
565
+ else:
566
+ return int(nbinsz*factorial(nbinsz+order-1)/(factorial(nbinsz)*factorial(order)))
567
+
568
+ def _cumnzcombis_order(self, order, nbinsz, dotomo):
569
+ res = 0
570
+ for order in range(1, order+1):
571
+ res += self._nzcombis_order(order, nbinsz, dotomo)
572
+ return res
573
+
574
+ def genzcombi(self, zs, nbinsz=None):
575
+ """ Returns index of tomographic bin combination of Map^n output.
576
+
577
+ Arguments:
578
+ ----------
579
+ zs: list of integers
580
+ Target combination of tomographic redshifts ([z1, ..., zk]).
581
+ nbinsz: int, optional
582
+ The number of tomographic bins in the computation of Map^n. If not set,
583
+ reverts to corresponding class attribute.
584
+
585
+ Returns
586
+ -------
587
+ zind_flat: int
588
+ Index of flattened Map^k(z1,...,zk) datavector in global output.
589
+ """
590
+
591
+ if self.combinatorics is None:
592
+ self.combinatorics = {}
593
+ for order in range(1,self.order_max+1):
594
+ self.combinatorics[order] = MapCombinatorics(nbinsz,order_max=order)
595
+
596
+ if nbinsz is None:
597
+ nbinsz = self.nbinsz
598
+ if nbinsz is None:
599
+ raise ValueError("No value for `nbinsz` has been allocated yet.")
600
+ if len(zs)>self.order_max:
601
+ raise ValueError("We only computed the statistics up to order %i."%self.order_max)
602
+ if max(zs) >= nbinsz:
603
+ raise ValueError("We only have %i tomographic bins available."%nbinsz)
604
+
605
+ order = len(zs)
606
+ zind_flat = self._cumnzcombis_order(order-1,nbinsz,True) + self.combinatorics[order].sel2ind(zs)
607
+
608
+ return zind_flat
609
+
610
+
611
+ def getmap(self, indR, cat, dotomo=True):
612
+ """ Computes various maps that are part of the basis of the Map^n estimator.
613
+
614
+ Arguments:
615
+ ----------
616
+ indR: int
617
+ Index of aperture radius for which maps are computed
618
+ cat: orpheus.SpinTracerCatalog
619
+ The catalog instance to be processed
620
+ dotomo: bool, optional
621
+ Whether the tomographic information in `cat` should be
622
+ used for the map construction
623
+
624
+ Returns:
625
+ --------
626
+ counts: ndarray
627
+ Aperture number counts
628
+
629
+ """
630
+
631
+ nbinsz = cat.nbinsz
632
+ if not dotomo:
633
+ nbinsz = 1
634
+
635
+ args = self._buildargs(cat, None, dotomo, indR, forfunc="EqualGrid")
636
+ ncenters_1 = args[3]
637
+ ncenters_2 = args[4]
638
+ self.clib.ApertureMassMap_Equal(*args)
639
+
640
+ counts = args[-6].reshape((nbinsz, 3, ncenters_2, ncenters_1))
641
+ covs = args[-5].reshape((2, ncenters_2, ncenters_1))
642
+ Msn = args[-4].reshape((nbinsz, self.order_max, ncenters_2, ncenters_1))
643
+ Sn = args[-3].reshape((nbinsz, self.order_max, ncenters_2, ncenters_1))
644
+ Mapn = args[-2].reshape((nbinsz, self.order_max, ncenters_2, ncenters_1))
645
+ Mapn_var = args[-1].reshape((nbinsz, self.order_max, ncenters_2, ncenters_1))
646
+
647
+ return counts, covs, Msn, Sn, Mapn, Mapn_var
648
+
649
+
650
+ class Direct_NapnEqual(DirectEstimator):
651
+ r"""
652
+ Compute direct estimator for equal-scale aperture counts statistics.
653
+
654
+ Attributes
655
+ ----------
656
+ order_max : int
657
+ Maximum order of the statistics to be computed.
658
+
659
+ Rmin : float
660
+ Minimum aperture radius.
661
+
662
+ Rmax : float
663
+ Maximum aperture radius.
664
+
665
+ field : str, optional
666
+ Type of input field (``"scalar"`` or ``"polar"``).
667
+
668
+ filter_form : str, optional
669
+ Filter type used in the aperture function (``"S98"``, ``"C02"``, ``"Sch04"``, etc.).
670
+
671
+ ap_weights : str, optional
672
+ Aperture weighting strategy (``"Identity"``, ``"InvShot"``).
673
+
674
+ **kwargs : dict
675
+ Additional keyword arguments passed to :class:`DirectEstimator`.
676
+
677
+ Notes
678
+ -----
679
+ Inherits all other parameters and attributes from :class:`DirectEstimator`.
680
+ Additional child-specific parameters can be passed via ``kwargs``.
681
+ """
682
+
683
+
684
+ def __init__(self, order_max, Rmin, Rmax, field="scalar", filter_form="C02", ap_weights="Identity", **kwargs):
685
+ super().__init__(Rmin=Rmin, Rmax=Rmax, **kwargs)
686
+ self.order_max = order_max
687
+ self.nbinsz = None
688
+ self.field = field
689
+ self.filter_form = filter_form
690
+ self.ap_weights = ap_weights
691
+
692
+ self.fields_avail = ["scalar", "polar"]
693
+ self.ap_weights_dict = {"Identity":0, "InvShot":1}
694
+ self.filters_dict = {"S98":0, "C02":1, "Sch04":2, "PolyExp":3}
695
+ self.ap_weights_avail = list(self.ap_weights_dict.keys())
696
+ self.filters_avail = list(self.filters_dict.keys())
697
+ assert(self.field in self.fields_avail)
698
+ assert(self.ap_weights in self.ap_weights_avail)
699
+ assert(self.filter_form in self.filters_avail)
700
+
701
+ # We do not need DoubleTree for equal-aperture estimator
702
+ if self.method=="DoubleTree":
703
+ self.method="Tree"
704
+
705
+ p_c128 = ndpointer(complex, flags="C_CONTIGUOUS")
706
+ p_f64 = ndpointer(np.float64, flags="C_CONTIGUOUS")
707
+ p_f32 = ndpointer(np.float32, flags="C_CONTIGUOUS")
708
+ p_i32 = ndpointer(np.int32, flags="C_CONTIGUOUS")
709
+ p_f64_nof = ndpointer(np.float64)
710
+
711
+ # Compute aperture counts map for equal-scale stats
712
+ self.clib.ApertureCountsMap_Equal.restype = ct.c_void_p
713
+ self.clib.ApertureCountsMap_Equal.argtypes = [
714
+ ct.c_double, p_f64, p_f64, ct.c_int32, ct.c_int32,
715
+ ct.c_int32, ct.c_int32, ct.c_int32, ct.c_int32, ct.c_double, ct.c_double,
716
+ p_f64, p_f64, p_f64, p_f64, p_f64, p_i32, ct.c_int32, ct.c_int32,
717
+ p_f64,
718
+ ct.c_double, ct.c_double, ct.c_double, ct.c_double, ct.c_int32, ct.c_int32,
719
+ p_i32, p_i32, p_i32,
720
+ ct.c_int32, p_f64, p_f64, p_f64, p_f64, p_f64, p_f64]
721
+
722
+ # Compute nth order equal-scale statistics using discrete estimator (E-Mode only!)
723
+ self.clib.NapnSingleDisc.restype = ct.c_void_p
724
+ self.clib.NapnSingleDisc.argtypes = [
725
+ ct.c_double, p_f64, p_f64, ct.c_int32,
726
+ ct.c_int32, ct.c_int32, ct.c_int32, ct.c_int32, ct.c_double, ct.c_double,
727
+ p_f64, p_f64, p_f64, p_f64, p_f64, p_i32, ct.c_int32, ct.c_int32,
728
+ p_f64, p_f64, ct.c_int32, ct.c_int32,
729
+ ct.c_double, ct.c_double, ct.c_double, ct.c_double, ct.c_int32, ct.c_int32,
730
+ p_i32, p_i32, p_i32,
731
+ ct.c_int32, p_f64, p_f64]
732
+
733
+ # Compute nth order equal-scale statistics using discrete estimator (E-Mode only!)
734
+ self.clib.singleAp_NapnSingleDisc.restype = ct.c_void_p
735
+ self.clib.singleAp_NapnSingleDisc.argtypes = [
736
+ ct.c_double, ct.c_double, ct.c_double,
737
+ ct.c_int32, ct.c_int32, ct.c_int32, ct.c_double, ct.c_double,
738
+ p_f64, p_f64, p_f64, p_f64, p_f64, p_i32, ct.c_int32, ct.c_int32,
739
+ p_f64,
740
+ ct.c_double, ct.c_double, ct.c_double, ct.c_double, ct.c_int32, ct.c_int32,
741
+ p_i32, p_i32, p_i32,
742
+ p_f64, p_f64, p_f64, p_f64]
743
+
744
+ def process(self, cat, dotomo=True, Nbar_policy=1, connected=True, dpix_innergrid=2.):
745
+ r"""
746
+ Computes aperture statistics on a catalog.
747
+
748
+ Parameters
749
+ ----------
750
+ cat : orpheus.SpinTracerCatalog
751
+ The catalog instance to be processed.
752
+ dotomo : bool, optional
753
+ Whether to compute the statistics for all tomographic bin combinations.
754
+ Default is True.
755
+ Nbar_policy : int, optional
756
+ What normalization to use:
757
+
758
+ 0 : Use local Nbar for normalization
759
+ 1 : Use global Nbar for normalization
760
+ 2 : No Nbar for normalization
761
+
762
+ Default is 1.
763
+ connected : bool, optional
764
+ Whether to output only the connected part of the aperture mass statistics.
765
+ Does not have an impact at the moment.
766
+ dpix_innergrid : float, optional
767
+ Pixel size for a rough reconstruction of the angular mask. Used to preselect
768
+ aperture centers in the interior of the survey.
769
+ Default is 2.
770
+
771
+ Returns
772
+ -------
773
+ None
774
+ Currently does not return any value.
775
+ """
776
+
777
+ assert(isinstance(cat, ScalarTracerCatalog))
778
+
779
+ nbinsz = cat.nbinsz
780
+ if not dotomo:
781
+ nbinsz = 1
782
+ self.nbinsz = nbinsz
783
+
784
+ nzcombis = self._nzcombis_tot(nbinsz,dotomo)
785
+ result_Napn = np.zeros((self.nbinsr, self.nfrac_covs, nzcombis), dtype=np.float64)
786
+ result_wNapn = np.zeros((self.nbinsr, self.nfrac_covs, nzcombis), dtype=np.float64)
787
+ if (self.method in ["Discrete", "BaseTree"]):
788
+ func = self.clib.NapnSingleDisc
789
+ elif (self.method in ["Discrete", "BaseTree"]):
790
+ raise NotImplementedError
791
+ else:
792
+ raise NotImplementedError
793
+
794
+ # Build a grid that only covers inner part of patch
795
+ # This will be used to preselelct aperture centers
796
+ args_innergrid = cat.togrid(fields=[cat.isinner], dpix=dpix_innergrid, method="NGP", normed=True, tomo=False)
797
+
798
+ for elr, R in enumerate(self.radii):
799
+ nextnap_out = np.zeros(nzcombis*self.nfrac_covs ,dtype=np.float64)
800
+ nextwnap_out = np.zeros(nzcombis*self.nfrac_covs ,dtype=np.float64)
801
+ args = self._buildargs(cat, args_innergrid, dotomo, Nbar_policy, elr, forfunc="Equal")
802
+ func(*args)
803
+ result_Napn[elr] = args[-2].reshape((self.nfrac_covs, nzcombis))[:]
804
+ result_wNapn[elr] = args[-1].reshape((self.nfrac_covs, nzcombis))[:]
805
+
806
+ sys.stdout.write("\rDone %i/%i aperture radii"%(elr+1,self.nbinsr))
807
+
808
+ return result_Napn, result_wNapn
809
+
810
+ def _getindex(self, order, mode, zcombi):
811
+ pass
812
+
813
+ def _buildargs(self, cat, args_innergrid, dotomo, Nbar_policy, indR, forfunc="Equal"):
814
+
815
+ assert(forfunc in ["Equal", "EqualGrid"])
816
+
817
+ if not dotomo:
818
+ nbinsz = 1
819
+ zbins = np.zeros(cat.ngal, dtype=np.int32)
820
+ else:
821
+ nbinsz = cat.nbinsz
822
+ zbins = cat.zbins.astype(np.int32)
823
+
824
+ # Initialize combinatorics instances for lateron
825
+ # TODO: Should find a better place where to put this...
826
+ self.combinatorics = {}
827
+ for order in range(1,self.order_max+1):
828
+ self.combinatorics[order] = MapCombinatorics(nbinsz,order_max=order)
829
+
830
+ # Parameters related to the aperture grid
831
+ if forfunc=="Equal":
832
+ # Get centers and check that they are in the interior of the inner catalog
833
+ centers_1, centers_2 = self.get_pixelization(cat, self.radii[indR], self.accuracies[indR], R_crop=0., mgrid=True)
834
+ _f, _s1, _s2, _dpixi, _, _, = args_innergrid
835
+ pixs_c = (((centers_2-_s2)//_dpixi)*_f[0,1].shape[1] + (centers_1-_s1)//_dpixi).astype(int)
836
+ sel_inner = _f[0,1]>0.
837
+ sel_centers = sel_inner.flatten()[pixs_c]
838
+ centers_1 = centers_1[sel_centers]
839
+ centers_2 = centers_2[sel_centers]
840
+ ncenters = len(centers_1)
841
+ elif forfunc=="EqualGrid":
842
+ # Get centers along each dimension
843
+ centers_1, centers_2 = self.get_pixelization(cat, self.radii[indR], self.accuracies[indR], R_crop=0., mgrid=False)
844
+ ncenters = len(centers_1)*len(centers_2)
845
+
846
+ #self.dpix_hash = max(0.25, self.radii[indR])
847
+ cat.build_spatialhash(dpix=self.dpix_hash, extent=[None, None, None, None])
848
+ hashgrid = FlatPixelGrid_2D(cat.pix1_start, cat.pix2_start,
849
+ cat.pix1_n, cat.pix2_n, cat.pix1_d, cat.pix2_d)
850
+ regridded_mask = cat.mask.regrid(hashgrid).data.flatten().astype(np.float64)
851
+
852
+ if forfunc=="Equal":
853
+ args_centers = (self.radii[indR], centers_1, centers_2, ncenters,)
854
+ elif forfunc=="EqualGrid":
855
+ args_centers = (self.radii[indR], centers_1, centers_2, len(centers_1), len(centers_2), )
856
+ if forfunc=="Equal":
857
+ args_ofw = (self.order_max, self.filters_dict[self.filter_form],
858
+ np.int32(self.multicountcorr), np.int32(Nbar_policy), np.float64(self.weight_outer), np.float64(self.weight_inpainted), )
859
+ elif forfunc=="EqualGrid":
860
+ args_ofw = (self.order_max, self.filters_dict[self.filter_form], self.ap_weights_dict[self.ap_weights],
861
+ np.float64(self.weight_outer), np.float64(self.weight_inpainted), )
862
+ args_cat = (cat.weight.astype(np.float64), cat.isinner.astype(np.float64),
863
+ cat.pos1.astype(np.float64), cat.pos2.astype(np.float64), cat.tracer.astype(np.float64),
864
+ zbins, np.int32(nbinsz), np.int32(cat.ngal), )
865
+ if forfunc=="Equal":
866
+ args_mask = (regridded_mask, self.frac_covs, self.nfrac_covs, 0, )
867
+ elif forfunc=="EqualGrid":
868
+ args_mask = (regridded_mask, )
869
+ args_hash = (np.float64(cat.pix1_start), np.float64(cat.pix2_start),
870
+ np.float64(cat.pix1_d), np.float64(cat.pix2_d),
871
+ np.int32(cat.pix1_n), np.int32(cat.pix2_n),
872
+ cat.index_matcher.astype(np.int32), cat.pixs_galind_bounds.astype(np.int32),
873
+ cat.pix_gals.astype(np.int32)
874
+ )
875
+ if forfunc=="Equal":
876
+ len_out = self._nzcombis_tot(nbinsz,dotomo)*self.nfrac_covs
877
+ args_out = (np.zeros(len_out).astype(np.float64), np.zeros(len_out).astype(np.float64))
878
+ elif forfunc=="EqualGrid":
879
+ args_out = (np.zeros(3*nbinsz*ncenters).astype(np.float64),
880
+ np.zeros(2*ncenters).astype(np.float64),
881
+ np.zeros(self.order_max*nbinsz*ncenters).astype(np.float64),
882
+ np.zeros(self.order_max*nbinsz*ncenters).astype(np.float64),
883
+ np.zeros(self.order_max*nbinsz*ncenters).astype(np.float64),
884
+ np.zeros(self.order_max*nbinsz*ncenters).astype(np.float64), )
885
+ # Return the parameters for the Nap computation
886
+ args = (*args_centers,
887
+ *args_ofw,
888
+ *args_cat,
889
+ *args_mask,
890
+ *args_hash,
891
+ np.int32(self.nthreads),
892
+ *args_out)
893
+ if False:
894
+ if forfunc=="Equal":func = self.clib.NapnSingleDisc
895
+ if forfunc=="EqualGrid":func = self.clib.ApertureCountsMap_Equal
896
+
897
+ for elarg, arg in enumerate(args):
898
+ toprint = (elarg, type(arg),)
899
+ if isinstance(arg, np.ndarray):
900
+ toprint += (type(arg[0]), arg.shape)
901
+ #try:
902
+ toprint += (func.argtypes[elarg], )
903
+ print(toprint)
904
+ print(arg)
905
+ #except:
906
+ # print("We did have a problem for arg %i"%elarg)
907
+
908
+ return args
909
+
910
+ def _nzcombis_tot(self, nbinsz, dotomo):
911
+ res = 0
912
+ for order in range(1, self.order_max+1):
913
+ res += self._nzcombis_order(order, nbinsz, dotomo)
914
+ return res
915
+
916
+ def _nzcombis_order(self, order, nbinsz, dotomo):
917
+ if not dotomo:
918
+ return 1
919
+ else:
920
+ return int(nbinsz*factorial(nbinsz+order-1)/(factorial(nbinsz)*factorial(order)))
921
+
922
+ def _cumnzcombis_order(self, order, nbinsz, dotomo):
923
+ res = 0
924
+ for order in range(1, order+1):
925
+ res += self._nzcombis_order(order, nbinsz, dotomo)
926
+ return res
927
+
928
+ def genzcombi(self, zs, nbinsz=None):
929
+ if nbinsz is None:
930
+ nbinsz = self.nbinsz
931
+ if nbinsz is None:
932
+ raise ValueError("No value for `nbinsz` has been allocated yet.")
933
+ if len(zs)>self.order_max:
934
+ raise ValueError("We only computed the statistics up to order %i."%self.order_max)
935
+ if max(zs) >= nbinsz:
936
+ raise ValueError("We only have %i tomographic bins available."%nbinsz)
937
+
938
+ order = len(zs)
939
+ return self._cumnzcombis_order(order-1,nbinsz,True) + self.combinatorics[order].sel2ind(zs)
940
+
941
+
942
+ def getnap(self, indR, cat, dotomo=True):
943
+ """ This simply computes an aperture mass map together with weights and coverages """
944
+ nbinsz = cat.nbinsz
945
+ if not dotomo:
946
+ nbinsz = 1
947
+
948
+ args = self._buildargs(cat, None, dotomo, indR, forfunc="EqualGrid")
949
+ ncenters_1 = args[3]
950
+ ncenters_2 = args[4]
951
+ self.clib.ApertureCountsMap_Equal(*args)
952
+
953
+ counts = args[-6].reshape((nbinsz, 3, ncenters_2, ncenters_1))
954
+ covs = args[-5].reshape((2, ncenters_2, ncenters_1))
955
+ Msn = args[-4].reshape((nbinsz, self.order_max, ncenters_2, ncenters_1))
956
+ Sn = args[-3].reshape((nbinsz, self.order_max, ncenters_2, ncenters_1))
957
+ Napn = args[-2].reshape((nbinsz, self.order_max, ncenters_2, ncenters_1))
958
+ Napn_counts = args[-1].reshape((nbinsz, self.order_max, ncenters_2, ncenters_1))
959
+
960
+ return counts, covs, Msn, Sn, Napn, Napn_counts
961
+
962
+
963
+ class MapCombinatorics:
964
+
965
+ def __init__(self, nradii, order_max):
966
+ self.nradii = nradii
967
+ self.order_max = order_max
968
+ self.psummem = None
969
+ self.nindices = self.psumtot(order_max+1, nradii)
970
+
971
+ def psumtot(self, n, m):
972
+ """ Calls to (n-1)-fold nested loop over m indicices
973
+ where i1 <= i2 <= ... <= in. This is equivalent to the
974
+ number of independent Map^i components over a range of
975
+ m radii (0<i<=n) as well as to the size of the multivariate
976
+ power sum set generating those multivariate cumulants.
977
+
978
+ It can alternatively be used do count through the flattened
979
+ redshift indices for a single-scale computation up until a
980
+ maximum order.
981
+
982
+ Example:
983
+ psumtot(m=10,n=4) gives the same result as the code
984
+ >>> res = 0
985
+ >>> for i1 in range(10):
986
+ >>> for i2 in range(i1,10):
987
+ >>> for i3 in range(i2,10):
988
+ >>> res += 1
989
+ >>> print(res)
990
+
991
+ Notes:
992
+ * The recursion reads as follows:
993
+ s(m,0) = 1
994
+ s(m,n) = sum_{i=1}^{m-1} s(m-1,n-1)
995
+ [Have not formally proved that but checked with pen and paper
996
+ up until n=3 on examples and the underlying geometry does make
997
+ sense. Testing against nested loops also works as long as the
998
+ loops can be computed in a sensible amount of time]
999
+ * As the solution is recusive and therefore might take long to
1000
+ compute we use a memoization technique to get rid of all of
1001
+ the unneccessary nested calls.
1002
+ """
1003
+
1004
+ assert(m<=self.nradii)
1005
+ assert(n<=self.order_max+1)
1006
+
1007
+ # For initial call allocate memo
1008
+ if self.psummem is None:
1009
+ self.psummem = np.zeros((n,m))#, dtype=np.int)
1010
+ self.psummem[0] = np.ones(m)
1011
+ # Base case
1012
+ if m<=0 or n<=0:
1013
+ return self.psummem[n,m]
1014
+ # Recover from memo
1015
+ if self.psummem[n-1,m-1] != 0:
1016
+ return self.psummem[n-1,m-1]
1017
+ # Add to memo
1018
+ else:
1019
+ res = 0
1020
+ for i in range(m):
1021
+ res += self.psumtot(n-1,m-i)
1022
+ self.psummem[n-1,m-1] = res
1023
+ return int(self.psummem[n-1,m-1])
1024
+
1025
+ def sel2ind(self, sel):
1026
+ """
1027
+ Assignes unique index to given selection in powr sum set
1028
+ Note that sel[0] <= sel[1] <= ... <= sel[self.nradii-1] is required!
1029
+ """
1030
+ # Check validity
1031
+ #assert(len(sel)==n-1)
1032
+ #for el in range(len(sel)-1):
1033
+ # assert(sel[el+1] >= sel[el])
1034
+ #assert(sel[-1] <= m)
1035
+
1036
+ i = 0
1037
+ ind = 0
1038
+ ind_sel = 0
1039
+ lsel = len(sel)
1040
+ while True:
1041
+ while i >= sel[ind_sel]:
1042
+ #print(i,ind_sel)
1043
+ ind_sel += 1
1044
+ if ind_sel >= lsel:
1045
+ return int(ind)
1046
+ ind += self.psummem[self.order_max-1-ind_sel, self.nradii-1-i]
1047
+ i += 1
1048
+
1049
+ return int(ind)
1050
+
1051
+
1052
+ def ind2sel(self, ind):
1053
+ """ Inverse of sel2ind...NOT WORKING YET """
1054
+
1055
+ sel = np.zeros(self.order_max)#, dtype=np.int)
1056
+ # Edge cases
1057
+ if ind==0:
1058
+ return sel.astype(np.int)
1059
+ if ind==1:
1060
+ sel[-1] = 1
1061
+ return sel.astype(np.int)
1062
+ if ind==self.nindices-1:
1063
+ return (self.nradii-1)*np.zeros(self.order_max, dtype=np.int)
1064
+
1065
+ tmpind = ind # Remainder of index in psum
1066
+ nextind_ax0 = self.order_max-1 # Value of i_k
1067
+ nextind_ax1 = self.nradii-1 # Helper
1068
+ tmpsel = 0 # Value of i_k
1069
+ indsel = 0 # Index in selection
1070
+ while True:
1071
+ nextsubs = 0
1072
+ while True:
1073
+ tmpsubs = self.psummem[nextind_ax0, nextind_ax1]
1074
+ #print(tmpind, nextsubs, tmpsubs, sel)
1075
+ if tmpind > nextsubs + tmpsubs:
1076
+ nextind_ax1 -= 1
1077
+ tmpsel += 1
1078
+ nextsubs += tmpsubs
1079
+ elif tmpind < nextsubs + tmpsubs:
1080
+ nextind_ax0 -= 1
1081
+ tmpind -= nextsubs
1082
+ sel[indsel] = tmpsel
1083
+ indsel += 1
1084
+ break
1085
+ else:
1086
+ sel[indsel:] = tmpsel + 1
1087
+ return sel.astype(np.int)
1088
+ if sel[-2] != 0:
1089
+ sel[-1] = sel[-2] + tmpind
1090
+ if sel[-1] != 0:
1091
+ return sel.astype(np.int)