orpheus-npcf 0.1.0__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.

Potentially problematic release.


This version of orpheus-npcf might be problematic. Click here for more details.

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