kNNpy.Auxiliary.TPCF.TracerField2DA

  1####################################################################################################
  2
  3#-------------------  These libraries are required for evaluating the functions  -------------------
  4
  5import numpy as np
  6import healpy as hp
  7from astropy.coordinates import SkyCoord
  8from astropy import units as u
  9from astropy_healpix import HEALPix
 10from astropy.coordinates import ICRS
 11import time
 12import copy
 13import sys
 14import os
 15
 16#Necessary for relative imports (see https://stackoverflow.com/questions/34478398/import-local-function-from-a-module-housed-in-another-directory-with-relative-im)
 17module_path = os.path.abspath(os.path.join('../../'))
 18'''
 19@private
 20'''
 21if module_path not in sys.path:
 22    sys.path.append(module_path)
 23
 24#Importing the required helper function
 25from kNNpy.HelperFunctions_2DA import create_smoothed_field_dict_2DA
 26
 27####################################################################################################
 28
 29#--------------------------------------  Function Definitions  -------------------------------------
 30
 31def CorrelationFunction(BinsRad, MaskedTracerPosRad, FieldSkymap, NR_ND=10, ReturnSmoothedDict=False, Verbose=False):
 32    
 33    r'''
 34    Computes the angular two-point cross-correlation function between the given set of discrete tracers (`MaskedTracerPosRad`) and the given continuous overdensity field (`FieldSkymap`) at the given angular distance scales (`BinsRad`).
 35
 36    Parameters
 37    ----------
 38    BinsRad : list of numpy float array
 39        array of angular distances (in radians) to compute the cross-correlation function at
 40    MaskedTracerPosRad : numpy float array of shape ``(n_tracer, 2)``
 41        array of sky locations for the discrete tracers. For each data point in the array, the first (second) coordinate should be the declination (right ascension) in radians. Please ensure ``-np.pi/2 <= declination <= pi/2`` and ``0 <= right ascension <= 2*np.pi``.
 42    FieldSkymap : numpy float array
 43        the healpy map of the continuous field. The values of the masked pixels, if any, should be set to `hp.UNSEEN`.
 44    NR_ND : int
 45        ratio of number of randoms to number of data points used in the 2PCF calculation to remove biases caused by the presence of an observational mask, by default ``10``. This is similar to the ratio of number of randoms to number data points used in the usual Landy-Szalay estimator of the 2PCF[^1]. See notes for a more detailed explanation of this parameter and how to set it appropriately.
 46    ReturnSmoothedDict : bool, optional
 47        if set to ``True``, the dictionary containing the continuous field masked within the observational footprint, and smoothed at the provided angular distance scales, will be returned along with the nearest-neighbour measurements, by default ``False``.
 48    Verbose : bool, optional
 49        if set to ``True``, the time taken to complete each step of the calculation will be printed, by default ``False``.
 50
 51    Returns
 52    -------
 53    w_theta: numpy float array of shape ``(len(Bins)-1, )``
 54        tracer-field two-point cross-correlation function evaluated at the desired distance bins. Note that the 2PCF can't be estimated at the last bin due to the nature of the algorithm (refer to notes for details).
 55    SmoothedFieldDict : dict
 56        dictionary containing the continuous field masked within the observational footprint and smoothed at the provided angular distance scales, returned only if `ReturnSmoothedDict` is ``True``. For example, ``SmoothedFieldDict['0.215']`` represents the continuous map smoothed at a scale of 0.215 radians.
 57
 58    Raises
 59    ------
 60    ValueError
 61        if declination of any of the tracer points is not in ``[-np.pi/2, np.pi/2]``.
 62    ValueError
 63        if right ascension of any of the tracer points is not in ``[0, 2*np.pi]``.
 64    ValueError
 65        if the given tracer points are not on a two-dimensional grid.
 66    ValueError
 67        if the `NR_ND` is not an integer.
 68
 69    See Also
 70    --------
 71    kNNpy.Auxilliary.TPCF.TracerField2DA.CorrelationFunction_DataVector : computes a data vector of tracer-field two-point cross-correlations in 2D for multiple realisations of the tracer set.
 72    kNNpy.Auxilliary.TPCF.3DTPCF_Tracer-Field.CrossCorr2pt : computes tracer-field two-point cross-correlations in 3D.
 73    kNNpy.kNN_2D_Ang.TracerFieldCross2DA : computes 2D angular tracer-field cross-correlations using the $k$NN formalism.
 74
 75    Notes
 76    -----
 77    Measures the angular two-point cross-correlation function (2pcf) between a set of discrete tracers and a continuous overdensity field using the spherical band-averaging method, as described in Gupta (2024)[^1]. This is a generalisation of the spherical shell-averaging method described in Banerjee & Abel (2023)[^2].
 78    
 79    Data with associated observational footprints are supported, in which case, only tracer positions within the footprint should be provided and the field should be masked appropriately. If the footprints of the tracer set and the field are different, a combined mask representing the intersection of the two footprints should be used. The field at the masked pixels is set to 0 (which is the mean value of an overdensity field) for the purposes of the 2PCF computation to prevent any biases due to the mask.
 80
 81    <enter description for the NR_ND parameter>
 82    <enter description of algorithm and why the 2PCF can't be estimated at the last bin>
 83
 84    References
 85    ----------
 86    [^1]: Kaustubh Rajesh Gupta, Spatial clustering of gravitational wave sources with k-nearest neighbour distributions, MS Thesis, Indian Institute of Science Education and Research Pune [Digital Repository](http://dr.iiserpune.ac.in:8080/xmlui/handle/123456789/8819)
 87
 88    [^2]: Arka Banerjee, Tom Abel, Tracer-field cross-correlations with k-nearest neighbour   distributions, [Monthly Notices of the Royal Astronomical Society](https://doi.org/10.1093/mnras/stac3813), Volume 519, Issue 4, March 2023, Pages 4856–4868
 89    '''
 90    
 91    #-----------------------------------------------------------------------------------------------
 92
 93    if Verbose: total_start_time = time.perf_counter()
 94
 95    #-----------------------------------------------------------------------------------------------
 96        
 97    #Step 0: Check all inputs are consistent with the function requirement
 98
 99    if Verbose: print('Checking inputs ...')
100
101    if np.any((MaskedTracerPosRad[:, 0]<-np.pi/2) | (MaskedTracerPosRad[:, 0]>np.pi/2)):
102        raise ValueError('Invalid tracer point position(s): please ensure -pi/2 <= declination <= pi/2.')
103
104    if np.any((MaskedTracerPosRad[:, 1]<0) | (MaskedTracerPosRad[:, 0]>2*np.pi)):
105        raise ValueError('Invalid tracer point position(s): please ensure 0 <= right ascension <= 2*pi.')
106
107    if MaskedTracerPosRad.shape[1]!=2: 
108        raise ValueError('Incorrect spatial dimension for tracers: array containing the tracer positions must be of shape (n_tracer, 2), where n_tracer is the number of tracers.')
109    
110    if not isinstance(NR_ND, int): 
111        raise ValueError("Please input an integer value for 'NR_ND'.")
112
113    if Verbose: print('\tdone.')
114
115    #-----------------------------------------------------------------------------------------------
116
117    #Getting the mask, sky coverage and HEALPix NSIDE
118
119    mask = np.ones_like(FieldSkymap).astype(int)
120    mask_ind = np.where(FieldSkymap==hp.UNSEEN)
121    mask[mask_ind] = 0
122    sky_frac = len(np.where(mask==1)[0])/len(mask)
123    NSIDE = hp.get_nside(FieldSkymap)
124
125    #-----------------------------------------------------------------------------------------------
126
127    #Calculate the smoothed field dictionary
128
129    if Verbose: 
130        smooth_start_time = time.perf_counter()
131        print('\nSmoothing the continuous field at the given angular distance scales...')
132
133    #Note: QueryMask is set to 2 everywhere to avoid masking out any additional pixels. The pixels outside the footprint are automatically masked during the smoothing process (refer to the HelperFunctions module documentation for more details).
134    SmoothedFieldDict = create_smoothed_field_dict_2DA(FieldSkymap, [BinsRad], query_mask=2*np.ones_like(FieldSkymap).astype(int), Verbose=False)
135
136    if Verbose: print('\tdone; time taken: {:.2e} s.'.format(time.perf_counter()-smooth_start_time))
137    
138    #-----------------------------------------------------------------------------------------------
139
140    #Computing the 2pcf
141
142    if Verbose: 
143        tpcf_start_time = time.perf_counter()
144        print('\nComputing the 2PCF...')
145
146    w_theta = np.zeros(len(BinsRad)-1)
147    
148    for i, ss in enumerate(BinsRad[:-1]):
149
150        sdgm_low = copy.deepcopy(SmoothedFieldDict[str(ss)])
151        sdgm_low[sdgm_low==hp.UNSEEN] = 0
152        sdgm_high = copy.deepcopy(SmoothedFieldDict[str(BinsRad[i+1])])
153        sdgm_high[sdgm_high==hp.UNSEEN] = 0
154
155        hp_object = HEALPix(nside=NSIDE, order='ring', frame=ICRS())
156
157        #Discrete tracers
158        ra = MaskedTracerPosRad[:, 1]*u.radian
159        dec = MaskedTracerPosRad[:, 0]*u.radian
160        coords = SkyCoord(ra, dec, frame='icrs')
161        ds2 = hp_object.interpolate_bilinear_skycoord(coords, sdgm_high)
162        ds1 = hp_object.interpolate_bilinear_skycoord(coords, sdgm_low)
163
164        #Randoms
165        n_rand = MaskedTracerPosRad.shape[0]*NR_ND
166        ra_rand = np.random.uniform(low=0, high=2*np.pi, size=2*int(n_rand/sky_frac))
167        dec_rand = np.arcsin(np.random.uniform(low=-1, high=1, size=2*int(n_rand/sky_frac)))
168        ipix = hp.ang2pix(NSIDE, np.rad2deg(ra_rand), np.rad2deg(dec_rand), nest=False, lonlat=True)
169        mask_val = mask[ipix]
170        ind_masked = np.where(mask_val==1)[0]
171        ra_rand_masked = ra_rand[ind_masked]
172        dec_rand_masked = dec_rand[ind_masked]
173        ind_ds = np.random.choice(len(ra_rand_masked), n_rand, replace=False)
174        ra_rand_masked_ds = ra_rand_masked[ind_ds]*u.radian
175        dec_rand_masked_ds = dec_rand_masked[ind_ds]*u.radian
176        coords_rand = SkyCoord(ra_rand_masked_ds, dec_rand_masked_ds, frame='icrs')
177        ds2_rand = hp_object.interpolate_bilinear_skycoord(coords_rand, sdgm_high)
178        ds1_rand = hp_object.interpolate_bilinear_skycoord(coords_rand, sdgm_low)
179        
180        A2 = 2*np.pi*(1-np.cos(BinsRad[i+1]))
181        A1 = 2*np.pi*(1-np.cos(BinsRad[i]))
182
183        w_theta[i] = np.mean((A2*ds2-A1*ds1)/(A2-A1)) - np.mean((A2*ds2_rand-A1*ds1_rand)/(A2-A1))
184
185    if Verbose: print('\tdone; time taken: {:.2e} s.'.format(time.perf_counter()-tpcf_start_time))
186
187    #-----------------------------------------------------------------------------------------------
188
189    if Verbose: print('\ntotal time taken: {:.2e} s.'.format(time.perf_counter()-total_start_time))
190    
191    if ReturnSmoothedDict: 
192        return w_theta, SmoothedFieldDict
193    else: 
194        return w_theta
195
196####################################################################################################
197    
198def CorrelationFunction_DataVector(BinsRad, MaskedTracerPosVectorRad, FieldSkymap, NR_ND=10, ReturnSmoothedDict=False, Verbose=False):
199    
200    r'''
201    Returns a 'data vector' of the angular two-point cross-correlation function between multiple realisations of the given set of discrete tracers (`MaskedTracerPosVectorRad`) and a single realisation of the given continuous overdensity field (`FieldSkymap`) at the given angular distance scales (`BinsRad`). Please refer to notes to understand why this might be useful.
202
203    Parameters
204    ----------
205    BinsRad : list of numpy float array
206        array of angular distances (in radians) to compute the cross-correlation function at
207    MaskedTracerPosVectorRad : numpy float array of shape ``(n_realisations, n_tracer, 2)``
208        array of sky locations for the first set of discrete tracers. For each data point in the array, the first (second) coordinate should be the declination (right ascension) in radians. Please ensure ``-np.pi/2 <= declination <= pi/2`` and ``0 <= right ascension <= 2*np.pi``.
209    FieldSkymap : numpy float array
210        the healpy map of the continuous field. The values of the masked pixels, if any, should be set to `hp.UNSEEN`.
211    NR_ND : int
212        ratio of number of randoms to number of data points used in the 2PCF calculation to remove biases caused by the presence of an observational mask, by default ``10``. This is similar to the ratio of number of randoms to number data points used in the usual Landy-Szalay estimator of the 2PCF[^1]. See notes for a more detailed explanation of this parameter and how to set it appropriately.
213    ReturnSmoothedDict : bool, optional
214        if set to ``True``, the dictionary containing the continuous field masked within the observational footprint, and smoothed at the provided angular distance scales, will be returned along with the nearest-neighbour measurements, by default ``False``.
215    Verbose : bool, optional
216        if set to ``True``, the time taken to complete each step of the calculation will be printed, by default ``False``.
217
218    Returns
219    -------
220    w_theta_vector: numpy float array of shape ``(n_realisations, len(BinsRad)-1)``
221        data vector containing the tracer-field two-point cross-correlation function for multiple realisations of the discrete tracer set evaluated at the desired distance bins. Note that the 2PCF can't be estimated at the last bin due to the nature of the algorithm (refer to notes for details).
222    SmoothedFieldDict : dict
223        dictionary containing the continuous field masked within the observational footprint and smoothed at the provided angular distance scales, returned only if `ReturnSmoothedDict` is ``True``. For example, ``SmoothedFieldDict['0.215']`` represents the continuous map smoothed at a scale of 0.215 radians.
224
225    Raises
226    ------
227    ValueError
228        if declination of any of the tracer points is not in ``[-np.pi/2, np.pi/2]``.
229    ValueError
230        if right ascension of any of the tracer points is not in ``[0, 2*np.pi]``.
231    ValueError
232        if the given tracer points are not on a two-dimensional grid.
233    ValueError
234        if the `NR_ND` is not an integer.
235
236    See Also
237    --------
238    kNNpy.Auxilliary.TPCF.TracerField2DA.CorrelationFunction : computes tracer-field two-point cross-correlations in 2D for a single realisation of the tracer set.
239    kNNpy.Auxilliary.TPCF.3DTPCF_Tracer-Field.CrossCorr2pt : computes tracer-field two-point cross-correlations in 3D.
240    kNNpy.kNN_2D_Ang.TracerFieldCross2DA_DataVector : computes a data vector of 2D angular tracer-field cross-correlations for multiple tracer realisations using the $k$NN formalism.
241
242    Notes
243    -----
244    Please refer to the documentation of kNNpy.Auxilliary.TPCF.TracerFieldCross2DA.CorrelationFunction for important usage notes that also apply to this function and references. <Explain why cross-correlating multiple realisations of tracer with single realisation of field might be useful>
245
246    References
247    ----------
248    [^1]: Kaustubh Rajesh Gupta, Spatial clustering of gravitational wave sources with k-nearest neighbour distributions, MS Thesis, Indian Institute of Science Education and Research Pune [Digital Repository](http://dr.iiserpune.ac.in:8080/xmlui/handle/123456789/8819)
249    '''
250    
251    #-----------------------------------------------------------------------------------------------
252
253    if Verbose: total_start_time = time.perf_counter()
254
255    #-----------------------------------------------------------------------------------------------
256        
257    #Step 0: Check all inputs are consistent with the function requirement
258
259    if Verbose: print('Checking inputs ...')
260
261    if np.any((MaskedTracerPosVectorRad[:, :, 0]<-np.pi/2) | (MaskedTracerPosVectorRad[:, :, 0]>np.pi/2)):
262        raise ValueError('Invalid tracer point position(s): please ensure -pi/2 <= declination <= pi/2.')
263
264    if np.any((MaskedTracerPosVectorRad[:, :, 1]<0) | (MaskedTracerPosVectorRad[:, :, 0]>2*np.pi)):
265        raise ValueError('Invalid tracer point position(s): please ensure 0 <= right ascension <= 2*pi.')
266
267    if MaskedTracerPosVectorRad.shape[2]!=2: 
268        raise ValueError('Incorrect spatial dimension for tracers: array containing the tracer positions must be of shape (n_tracer, 2), where n_tracer is the number of tracers.')
269    
270    if not isinstance(NR_ND, int): 
271        raise ValueError("Please input an integer value for 'NR_ND'.")
272
273    if Verbose: print('\tdone.')
274
275    #-----------------------------------------------------------------------------------------------
276
277    #Getting the mask, sky coverage and HEALPix NSIDE
278
279    mask = np.ones_like(FieldSkymap).astype(int)
280    mask_ind = np.where(FieldSkymap==hp.UNSEEN)
281    mask[mask_ind] = 0
282    sky_frac = len(np.where(mask==1)[0])/len(mask)
283    NSIDE = hp.get_nside(FieldSkymap)
284
285    #-----------------------------------------------------------------------------------------------
286
287    #Calculate the smoothed field dictionary
288
289    if Verbose: 
290        smooth_start_time = time.perf_counter()
291        print('\nSmoothing the continuous field at the given angular distance scales...')
292
293    #Note: QueryMask is set to 2 everywhere to avoid masking out any additional pixels. The pixels outside the footprint are automatically masked during the smoothing process (refer to the HelperFunctions module documentation for more details).
294    SmoothedFieldDict = create_smoothed_field_dict_2DA(FieldSkymap, [BinsRad], query_mask=2*np.ones_like(FieldSkymap).astype(int), Verbose=False)
295
296    if Verbose: print('\tdone; time taken: {:.2e} s.'.format(time.perf_counter()-smooth_start_time))
297    
298    #-----------------------------------------------------------------------------------------------
299
300    #Looping the 2PCF calculation over the multiple realisations
301
302    n_reals = MaskedTracerPosVectorRad.shape[0]
303    w_theta_vector = np.zeros((n_reals, len(BinsRad)-1))
304
305    for realisation, MaskedTracerPosRad in enumerate(MaskedTracerPosVectorRad):
306
307        if Verbose:
308            start_time_real = time.perf_counter()
309            print(f'\n\n--------------  Realisation {realisation+1}/{n_reals}  --------------\n')
310
311        #-------------------------------------------------------------------------------------------
312
313        #Computing the 2pcf
314
315        if Verbose: 
316            print('Computing the 2PCF...')
317
318        w_theta_vector[realisation] = np.zeros(len(BinsRad)-1)
319        
320        for i, ss in enumerate(BinsRad[:-1]):
321
322            sdgm_low = copy.deepcopy(SmoothedFieldDict[str(ss)])
323            sdgm_low[sdgm_low==hp.UNSEEN] = 0
324            sdgm_high = copy.deepcopy(SmoothedFieldDict[str(BinsRad[i+1])])
325            sdgm_high[sdgm_high==hp.UNSEEN] = 0
326
327            hp_object = HEALPix(nside=NSIDE, order='ring', frame=ICRS())
328
329            #Discrete tracers
330            ra = MaskedTracerPosRad[:, 1]*u.radian
331            dec = MaskedTracerPosRad[:, 0]*u.radian
332            coords = SkyCoord(ra, dec, frame='icrs')
333            ds2 = hp_object.interpolate_bilinear_skycoord(coords, sdgm_high)
334            ds1 = hp_object.interpolate_bilinear_skycoord(coords, sdgm_low)
335
336            #Randoms
337            n_rand = MaskedTracerPosRad.shape[0]*NR_ND
338            ra_rand = np.random.uniform(low=0, high=2*np.pi, size=2*int(n_rand/sky_frac))
339            dec_rand = np.arcsin(np.random.uniform(low=-1, high=1, size=2*int(n_rand/sky_frac)))
340            ipix = hp.ang2pix(NSIDE, np.rad2deg(ra_rand), np.rad2deg(dec_rand), nest=False, lonlat=True)
341            mask_val = mask[ipix]
342            ind_masked = np.where(mask_val==1)[0]
343            ra_rand_masked = ra_rand[ind_masked]
344            dec_rand_masked = dec_rand[ind_masked]
345            ind_ds = np.random.choice(len(ra_rand_masked), n_rand, replace=False)
346            ra_rand_masked_ds = ra_rand_masked[ind_ds]*u.radian
347            dec_rand_masked_ds = dec_rand_masked[ind_ds]*u.radian
348            coords_rand = SkyCoord(ra_rand_masked_ds, dec_rand_masked_ds, frame='icrs')
349            ds2_rand = hp_object.interpolate_bilinear_skycoord(coords_rand, sdgm_high)
350            ds1_rand = hp_object.interpolate_bilinear_skycoord(coords_rand, sdgm_low)
351            
352            A2 = 2*np.pi*(1-np.cos(BinsRad[i+1]))
353            A1 = 2*np.pi*(1-np.cos(BinsRad[i]))
354
355            w_theta_vector[realisation, i] = np.mean((A2*ds2-A1*ds1)/(A2-A1)) - np.mean((A2*ds2_rand-A1*ds1_rand)/(A2-A1))
356
357        if Verbose: print('\tdone. time taken for realisation {}: {:.2e} s.'.format(realisation+1, time.perf_counter()-start_time_real))
358
359    #-----------------------------------------------------------------------------------------------
360
361    if Verbose: print('\ntotal time taken: {:.2e} s.'.format(time.perf_counter()-total_start_time))
362    
363    if ReturnSmoothedDict: 
364        return w_theta_vector, SmoothedFieldDict
365    else: 
366        return w_theta_vector
367
368####################################################################################################
369
370#----------------------------------------  END OF PROGRAM!  ----------------------------------------
371
372####################################################################################################
def CorrelationFunction( BinsRad, MaskedTracerPosRad, FieldSkymap, NR_ND=10, ReturnSmoothedDict=False, Verbose=False):
 32def CorrelationFunction(BinsRad, MaskedTracerPosRad, FieldSkymap, NR_ND=10, ReturnSmoothedDict=False, Verbose=False):
 33    
 34    r'''
 35    Computes the angular two-point cross-correlation function between the given set of discrete tracers (`MaskedTracerPosRad`) and the given continuous overdensity field (`FieldSkymap`) at the given angular distance scales (`BinsRad`).
 36
 37    Parameters
 38    ----------
 39    BinsRad : list of numpy float array
 40        array of angular distances (in radians) to compute the cross-correlation function at
 41    MaskedTracerPosRad : numpy float array of shape ``(n_tracer, 2)``
 42        array of sky locations for the discrete tracers. For each data point in the array, the first (second) coordinate should be the declination (right ascension) in radians. Please ensure ``-np.pi/2 <= declination <= pi/2`` and ``0 <= right ascension <= 2*np.pi``.
 43    FieldSkymap : numpy float array
 44        the healpy map of the continuous field. The values of the masked pixels, if any, should be set to `hp.UNSEEN`.
 45    NR_ND : int
 46        ratio of number of randoms to number of data points used in the 2PCF calculation to remove biases caused by the presence of an observational mask, by default ``10``. This is similar to the ratio of number of randoms to number data points used in the usual Landy-Szalay estimator of the 2PCF[^1]. See notes for a more detailed explanation of this parameter and how to set it appropriately.
 47    ReturnSmoothedDict : bool, optional
 48        if set to ``True``, the dictionary containing the continuous field masked within the observational footprint, and smoothed at the provided angular distance scales, will be returned along with the nearest-neighbour measurements, by default ``False``.
 49    Verbose : bool, optional
 50        if set to ``True``, the time taken to complete each step of the calculation will be printed, by default ``False``.
 51
 52    Returns
 53    -------
 54    w_theta: numpy float array of shape ``(len(Bins)-1, )``
 55        tracer-field two-point cross-correlation function evaluated at the desired distance bins. Note that the 2PCF can't be estimated at the last bin due to the nature of the algorithm (refer to notes for details).
 56    SmoothedFieldDict : dict
 57        dictionary containing the continuous field masked within the observational footprint and smoothed at the provided angular distance scales, returned only if `ReturnSmoothedDict` is ``True``. For example, ``SmoothedFieldDict['0.215']`` represents the continuous map smoothed at a scale of 0.215 radians.
 58
 59    Raises
 60    ------
 61    ValueError
 62        if declination of any of the tracer points is not in ``[-np.pi/2, np.pi/2]``.
 63    ValueError
 64        if right ascension of any of the tracer points is not in ``[0, 2*np.pi]``.
 65    ValueError
 66        if the given tracer points are not on a two-dimensional grid.
 67    ValueError
 68        if the `NR_ND` is not an integer.
 69
 70    See Also
 71    --------
 72    kNNpy.Auxilliary.TPCF.TracerField2DA.CorrelationFunction_DataVector : computes a data vector of tracer-field two-point cross-correlations in 2D for multiple realisations of the tracer set.
 73    kNNpy.Auxilliary.TPCF.3DTPCF_Tracer-Field.CrossCorr2pt : computes tracer-field two-point cross-correlations in 3D.
 74    kNNpy.kNN_2D_Ang.TracerFieldCross2DA : computes 2D angular tracer-field cross-correlations using the $k$NN formalism.
 75
 76    Notes
 77    -----
 78    Measures the angular two-point cross-correlation function (2pcf) between a set of discrete tracers and a continuous overdensity field using the spherical band-averaging method, as described in Gupta (2024)[^1]. This is a generalisation of the spherical shell-averaging method described in Banerjee & Abel (2023)[^2].
 79    
 80    Data with associated observational footprints are supported, in which case, only tracer positions within the footprint should be provided and the field should be masked appropriately. If the footprints of the tracer set and the field are different, a combined mask representing the intersection of the two footprints should be used. The field at the masked pixels is set to 0 (which is the mean value of an overdensity field) for the purposes of the 2PCF computation to prevent any biases due to the mask.
 81
 82    <enter description for the NR_ND parameter>
 83    <enter description of algorithm and why the 2PCF can't be estimated at the last bin>
 84
 85    References
 86    ----------
 87    [^1]: Kaustubh Rajesh Gupta, Spatial clustering of gravitational wave sources with k-nearest neighbour distributions, MS Thesis, Indian Institute of Science Education and Research Pune [Digital Repository](http://dr.iiserpune.ac.in:8080/xmlui/handle/123456789/8819)
 88
 89    [^2]: Arka Banerjee, Tom Abel, Tracer-field cross-correlations with k-nearest neighbour   distributions, [Monthly Notices of the Royal Astronomical Society](https://doi.org/10.1093/mnras/stac3813), Volume 519, Issue 4, March 2023, Pages 4856–4868
 90    '''
 91    
 92    #-----------------------------------------------------------------------------------------------
 93
 94    if Verbose: total_start_time = time.perf_counter()
 95
 96    #-----------------------------------------------------------------------------------------------
 97        
 98    #Step 0: Check all inputs are consistent with the function requirement
 99
100    if Verbose: print('Checking inputs ...')
101
102    if np.any((MaskedTracerPosRad[:, 0]<-np.pi/2) | (MaskedTracerPosRad[:, 0]>np.pi/2)):
103        raise ValueError('Invalid tracer point position(s): please ensure -pi/2 <= declination <= pi/2.')
104
105    if np.any((MaskedTracerPosRad[:, 1]<0) | (MaskedTracerPosRad[:, 0]>2*np.pi)):
106        raise ValueError('Invalid tracer point position(s): please ensure 0 <= right ascension <= 2*pi.')
107
108    if MaskedTracerPosRad.shape[1]!=2: 
109        raise ValueError('Incorrect spatial dimension for tracers: array containing the tracer positions must be of shape (n_tracer, 2), where n_tracer is the number of tracers.')
110    
111    if not isinstance(NR_ND, int): 
112        raise ValueError("Please input an integer value for 'NR_ND'.")
113
114    if Verbose: print('\tdone.')
115
116    #-----------------------------------------------------------------------------------------------
117
118    #Getting the mask, sky coverage and HEALPix NSIDE
119
120    mask = np.ones_like(FieldSkymap).astype(int)
121    mask_ind = np.where(FieldSkymap==hp.UNSEEN)
122    mask[mask_ind] = 0
123    sky_frac = len(np.where(mask==1)[0])/len(mask)
124    NSIDE = hp.get_nside(FieldSkymap)
125
126    #-----------------------------------------------------------------------------------------------
127
128    #Calculate the smoothed field dictionary
129
130    if Verbose: 
131        smooth_start_time = time.perf_counter()
132        print('\nSmoothing the continuous field at the given angular distance scales...')
133
134    #Note: QueryMask is set to 2 everywhere to avoid masking out any additional pixels. The pixels outside the footprint are automatically masked during the smoothing process (refer to the HelperFunctions module documentation for more details).
135    SmoothedFieldDict = create_smoothed_field_dict_2DA(FieldSkymap, [BinsRad], query_mask=2*np.ones_like(FieldSkymap).astype(int), Verbose=False)
136
137    if Verbose: print('\tdone; time taken: {:.2e} s.'.format(time.perf_counter()-smooth_start_time))
138    
139    #-----------------------------------------------------------------------------------------------
140
141    #Computing the 2pcf
142
143    if Verbose: 
144        tpcf_start_time = time.perf_counter()
145        print('\nComputing the 2PCF...')
146
147    w_theta = np.zeros(len(BinsRad)-1)
148    
149    for i, ss in enumerate(BinsRad[:-1]):
150
151        sdgm_low = copy.deepcopy(SmoothedFieldDict[str(ss)])
152        sdgm_low[sdgm_low==hp.UNSEEN] = 0
153        sdgm_high = copy.deepcopy(SmoothedFieldDict[str(BinsRad[i+1])])
154        sdgm_high[sdgm_high==hp.UNSEEN] = 0
155
156        hp_object = HEALPix(nside=NSIDE, order='ring', frame=ICRS())
157
158        #Discrete tracers
159        ra = MaskedTracerPosRad[:, 1]*u.radian
160        dec = MaskedTracerPosRad[:, 0]*u.radian
161        coords = SkyCoord(ra, dec, frame='icrs')
162        ds2 = hp_object.interpolate_bilinear_skycoord(coords, sdgm_high)
163        ds1 = hp_object.interpolate_bilinear_skycoord(coords, sdgm_low)
164
165        #Randoms
166        n_rand = MaskedTracerPosRad.shape[0]*NR_ND
167        ra_rand = np.random.uniform(low=0, high=2*np.pi, size=2*int(n_rand/sky_frac))
168        dec_rand = np.arcsin(np.random.uniform(low=-1, high=1, size=2*int(n_rand/sky_frac)))
169        ipix = hp.ang2pix(NSIDE, np.rad2deg(ra_rand), np.rad2deg(dec_rand), nest=False, lonlat=True)
170        mask_val = mask[ipix]
171        ind_masked = np.where(mask_val==1)[0]
172        ra_rand_masked = ra_rand[ind_masked]
173        dec_rand_masked = dec_rand[ind_masked]
174        ind_ds = np.random.choice(len(ra_rand_masked), n_rand, replace=False)
175        ra_rand_masked_ds = ra_rand_masked[ind_ds]*u.radian
176        dec_rand_masked_ds = dec_rand_masked[ind_ds]*u.radian
177        coords_rand = SkyCoord(ra_rand_masked_ds, dec_rand_masked_ds, frame='icrs')
178        ds2_rand = hp_object.interpolate_bilinear_skycoord(coords_rand, sdgm_high)
179        ds1_rand = hp_object.interpolate_bilinear_skycoord(coords_rand, sdgm_low)
180        
181        A2 = 2*np.pi*(1-np.cos(BinsRad[i+1]))
182        A1 = 2*np.pi*(1-np.cos(BinsRad[i]))
183
184        w_theta[i] = np.mean((A2*ds2-A1*ds1)/(A2-A1)) - np.mean((A2*ds2_rand-A1*ds1_rand)/(A2-A1))
185
186    if Verbose: print('\tdone; time taken: {:.2e} s.'.format(time.perf_counter()-tpcf_start_time))
187
188    #-----------------------------------------------------------------------------------------------
189
190    if Verbose: print('\ntotal time taken: {:.2e} s.'.format(time.perf_counter()-total_start_time))
191    
192    if ReturnSmoothedDict: 
193        return w_theta, SmoothedFieldDict
194    else: 
195        return w_theta

Computes the angular two-point cross-correlation function between the given set of discrete tracers (MaskedTracerPosRad) and the given continuous overdensity field (FieldSkymap) at the given angular distance scales (BinsRad).

Parameters
  • BinsRad (list of numpy float array): array of angular distances (in radians) to compute the cross-correlation function at
  • MaskedTracerPosRad (numpy float array of shape (n_tracer, 2)): array of sky locations for the discrete tracers. For each data point in the array, the first (second) coordinate should be the declination (right ascension) in radians. Please ensure -np.pi/2 <= declination <= pi/2 and 0 <= right ascension <= 2*np.pi.
  • FieldSkymap (numpy float array): the healpy map of the continuous field. The values of the masked pixels, if any, should be set to hp.UNSEEN.
  • NR_ND (int): ratio of number of randoms to number of data points used in the 2PCF calculation to remove biases caused by the presence of an observational mask, by default 10. This is similar to the ratio of number of randoms to number data points used in the usual Landy-Szalay estimator of the 2PCF1. See notes for a more detailed explanation of this parameter and how to set it appropriately.
  • ReturnSmoothedDict (bool, optional): if set to True, the dictionary containing the continuous field masked within the observational footprint, and smoothed at the provided angular distance scales, will be returned along with the nearest-neighbour measurements, by default False.
  • Verbose (bool, optional): if set to True, the time taken to complete each step of the calculation will be printed, by default False.
Returns
  • w_theta (numpy float array of shape (len(Bins)-1, )): tracer-field two-point cross-correlation function evaluated at the desired distance bins. Note that the 2PCF can't be estimated at the last bin due to the nature of the algorithm (refer to notes for details).
  • SmoothedFieldDict (dict): dictionary containing the continuous field masked within the observational footprint and smoothed at the provided angular distance scales, returned only if ReturnSmoothedDict is True. For example, SmoothedFieldDict['0.215'] represents the continuous map smoothed at a scale of 0.215 radians.
Raises
  • ValueError: if declination of any of the tracer points is not in [-np.pi/2, np.pi/2].
  • ValueError: if right ascension of any of the tracer points is not in [0, 2*np.pi].
  • ValueError: if the given tracer points are not on a two-dimensional grid.
  • ValueError: if the NR_ND is not an integer.
See Also

kNNpy.Auxilliary.TPCF.TracerField2DA.CorrelationFunction_DataVector: computes a data vector of tracer-field two-point cross-correlations in 2D for multiple realisations of the tracer set.
kNNpy.Auxilliary.TPCF.3DTPCF_Tracer-Field.CrossCorr2pt: computes tracer-field two-point cross-correlations in 3D.
kNNpy.kNN_2D_Ang.TracerFieldCross2DA: computes 2D angular tracer-field cross-correlations using the $k$NN formalism.

Notes

Measures the angular two-point cross-correlation function (2pcf) between a set of discrete tracers and a continuous overdensity field using the spherical band-averaging method, as described in Gupta (2024)2. This is a generalisation of the spherical shell-averaging method described in Banerjee & Abel (2023)3.

Data with associated observational footprints are supported, in which case, only tracer positions within the footprint should be provided and the field should be masked appropriately. If the footprints of the tracer set and the field are different, a combined mask representing the intersection of the two footprints should be used. The field at the masked pixels is set to 0 (which is the mean value of an overdensity field) for the purposes of the 2PCF computation to prevent any biases due to the mask.

References

  1. Kaustubh Rajesh Gupta, Spatial clustering of gravitational wave sources with k-nearest neighbour distributions, MS Thesis, Indian Institute of Science Education and Research Pune Digital Repository 

  2. Kaustubh Rajesh Gupta, Spatial clustering of gravitational wave sources with k-nearest neighbour distributions, MS Thesis, Indian Institute of Science Education and Research Pune Digital Repository 

  3. Arka Banerjee, Tom Abel, Tracer-field cross-correlations with k-nearest neighbour distributions, Monthly Notices of the Royal Astronomical Society, Volume 519, Issue 4, March 2023, Pages 4856–4868 

def CorrelationFunction_DataVector( BinsRad, MaskedTracerPosVectorRad, FieldSkymap, NR_ND=10, ReturnSmoothedDict=False, Verbose=False):
199def CorrelationFunction_DataVector(BinsRad, MaskedTracerPosVectorRad, FieldSkymap, NR_ND=10, ReturnSmoothedDict=False, Verbose=False):
200    
201    r'''
202    Returns a 'data vector' of the angular two-point cross-correlation function between multiple realisations of the given set of discrete tracers (`MaskedTracerPosVectorRad`) and a single realisation of the given continuous overdensity field (`FieldSkymap`) at the given angular distance scales (`BinsRad`). Please refer to notes to understand why this might be useful.
203
204    Parameters
205    ----------
206    BinsRad : list of numpy float array
207        array of angular distances (in radians) to compute the cross-correlation function at
208    MaskedTracerPosVectorRad : numpy float array of shape ``(n_realisations, n_tracer, 2)``
209        array of sky locations for the first set of discrete tracers. For each data point in the array, the first (second) coordinate should be the declination (right ascension) in radians. Please ensure ``-np.pi/2 <= declination <= pi/2`` and ``0 <= right ascension <= 2*np.pi``.
210    FieldSkymap : numpy float array
211        the healpy map of the continuous field. The values of the masked pixels, if any, should be set to `hp.UNSEEN`.
212    NR_ND : int
213        ratio of number of randoms to number of data points used in the 2PCF calculation to remove biases caused by the presence of an observational mask, by default ``10``. This is similar to the ratio of number of randoms to number data points used in the usual Landy-Szalay estimator of the 2PCF[^1]. See notes for a more detailed explanation of this parameter and how to set it appropriately.
214    ReturnSmoothedDict : bool, optional
215        if set to ``True``, the dictionary containing the continuous field masked within the observational footprint, and smoothed at the provided angular distance scales, will be returned along with the nearest-neighbour measurements, by default ``False``.
216    Verbose : bool, optional
217        if set to ``True``, the time taken to complete each step of the calculation will be printed, by default ``False``.
218
219    Returns
220    -------
221    w_theta_vector: numpy float array of shape ``(n_realisations, len(BinsRad)-1)``
222        data vector containing the tracer-field two-point cross-correlation function for multiple realisations of the discrete tracer set evaluated at the desired distance bins. Note that the 2PCF can't be estimated at the last bin due to the nature of the algorithm (refer to notes for details).
223    SmoothedFieldDict : dict
224        dictionary containing the continuous field masked within the observational footprint and smoothed at the provided angular distance scales, returned only if `ReturnSmoothedDict` is ``True``. For example, ``SmoothedFieldDict['0.215']`` represents the continuous map smoothed at a scale of 0.215 radians.
225
226    Raises
227    ------
228    ValueError
229        if declination of any of the tracer points is not in ``[-np.pi/2, np.pi/2]``.
230    ValueError
231        if right ascension of any of the tracer points is not in ``[0, 2*np.pi]``.
232    ValueError
233        if the given tracer points are not on a two-dimensional grid.
234    ValueError
235        if the `NR_ND` is not an integer.
236
237    See Also
238    --------
239    kNNpy.Auxilliary.TPCF.TracerField2DA.CorrelationFunction : computes tracer-field two-point cross-correlations in 2D for a single realisation of the tracer set.
240    kNNpy.Auxilliary.TPCF.3DTPCF_Tracer-Field.CrossCorr2pt : computes tracer-field two-point cross-correlations in 3D.
241    kNNpy.kNN_2D_Ang.TracerFieldCross2DA_DataVector : computes a data vector of 2D angular tracer-field cross-correlations for multiple tracer realisations using the $k$NN formalism.
242
243    Notes
244    -----
245    Please refer to the documentation of kNNpy.Auxilliary.TPCF.TracerFieldCross2DA.CorrelationFunction for important usage notes that also apply to this function and references. <Explain why cross-correlating multiple realisations of tracer with single realisation of field might be useful>
246
247    References
248    ----------
249    [^1]: Kaustubh Rajesh Gupta, Spatial clustering of gravitational wave sources with k-nearest neighbour distributions, MS Thesis, Indian Institute of Science Education and Research Pune [Digital Repository](http://dr.iiserpune.ac.in:8080/xmlui/handle/123456789/8819)
250    '''
251    
252    #-----------------------------------------------------------------------------------------------
253
254    if Verbose: total_start_time = time.perf_counter()
255
256    #-----------------------------------------------------------------------------------------------
257        
258    #Step 0: Check all inputs are consistent with the function requirement
259
260    if Verbose: print('Checking inputs ...')
261
262    if np.any((MaskedTracerPosVectorRad[:, :, 0]<-np.pi/2) | (MaskedTracerPosVectorRad[:, :, 0]>np.pi/2)):
263        raise ValueError('Invalid tracer point position(s): please ensure -pi/2 <= declination <= pi/2.')
264
265    if np.any((MaskedTracerPosVectorRad[:, :, 1]<0) | (MaskedTracerPosVectorRad[:, :, 0]>2*np.pi)):
266        raise ValueError('Invalid tracer point position(s): please ensure 0 <= right ascension <= 2*pi.')
267
268    if MaskedTracerPosVectorRad.shape[2]!=2: 
269        raise ValueError('Incorrect spatial dimension for tracers: array containing the tracer positions must be of shape (n_tracer, 2), where n_tracer is the number of tracers.')
270    
271    if not isinstance(NR_ND, int): 
272        raise ValueError("Please input an integer value for 'NR_ND'.")
273
274    if Verbose: print('\tdone.')
275
276    #-----------------------------------------------------------------------------------------------
277
278    #Getting the mask, sky coverage and HEALPix NSIDE
279
280    mask = np.ones_like(FieldSkymap).astype(int)
281    mask_ind = np.where(FieldSkymap==hp.UNSEEN)
282    mask[mask_ind] = 0
283    sky_frac = len(np.where(mask==1)[0])/len(mask)
284    NSIDE = hp.get_nside(FieldSkymap)
285
286    #-----------------------------------------------------------------------------------------------
287
288    #Calculate the smoothed field dictionary
289
290    if Verbose: 
291        smooth_start_time = time.perf_counter()
292        print('\nSmoothing the continuous field at the given angular distance scales...')
293
294    #Note: QueryMask is set to 2 everywhere to avoid masking out any additional pixels. The pixels outside the footprint are automatically masked during the smoothing process (refer to the HelperFunctions module documentation for more details).
295    SmoothedFieldDict = create_smoothed_field_dict_2DA(FieldSkymap, [BinsRad], query_mask=2*np.ones_like(FieldSkymap).astype(int), Verbose=False)
296
297    if Verbose: print('\tdone; time taken: {:.2e} s.'.format(time.perf_counter()-smooth_start_time))
298    
299    #-----------------------------------------------------------------------------------------------
300
301    #Looping the 2PCF calculation over the multiple realisations
302
303    n_reals = MaskedTracerPosVectorRad.shape[0]
304    w_theta_vector = np.zeros((n_reals, len(BinsRad)-1))
305
306    for realisation, MaskedTracerPosRad in enumerate(MaskedTracerPosVectorRad):
307
308        if Verbose:
309            start_time_real = time.perf_counter()
310            print(f'\n\n--------------  Realisation {realisation+1}/{n_reals}  --------------\n')
311
312        #-------------------------------------------------------------------------------------------
313
314        #Computing the 2pcf
315
316        if Verbose: 
317            print('Computing the 2PCF...')
318
319        w_theta_vector[realisation] = np.zeros(len(BinsRad)-1)
320        
321        for i, ss in enumerate(BinsRad[:-1]):
322
323            sdgm_low = copy.deepcopy(SmoothedFieldDict[str(ss)])
324            sdgm_low[sdgm_low==hp.UNSEEN] = 0
325            sdgm_high = copy.deepcopy(SmoothedFieldDict[str(BinsRad[i+1])])
326            sdgm_high[sdgm_high==hp.UNSEEN] = 0
327
328            hp_object = HEALPix(nside=NSIDE, order='ring', frame=ICRS())
329
330            #Discrete tracers
331            ra = MaskedTracerPosRad[:, 1]*u.radian
332            dec = MaskedTracerPosRad[:, 0]*u.radian
333            coords = SkyCoord(ra, dec, frame='icrs')
334            ds2 = hp_object.interpolate_bilinear_skycoord(coords, sdgm_high)
335            ds1 = hp_object.interpolate_bilinear_skycoord(coords, sdgm_low)
336
337            #Randoms
338            n_rand = MaskedTracerPosRad.shape[0]*NR_ND
339            ra_rand = np.random.uniform(low=0, high=2*np.pi, size=2*int(n_rand/sky_frac))
340            dec_rand = np.arcsin(np.random.uniform(low=-1, high=1, size=2*int(n_rand/sky_frac)))
341            ipix = hp.ang2pix(NSIDE, np.rad2deg(ra_rand), np.rad2deg(dec_rand), nest=False, lonlat=True)
342            mask_val = mask[ipix]
343            ind_masked = np.where(mask_val==1)[0]
344            ra_rand_masked = ra_rand[ind_masked]
345            dec_rand_masked = dec_rand[ind_masked]
346            ind_ds = np.random.choice(len(ra_rand_masked), n_rand, replace=False)
347            ra_rand_masked_ds = ra_rand_masked[ind_ds]*u.radian
348            dec_rand_masked_ds = dec_rand_masked[ind_ds]*u.radian
349            coords_rand = SkyCoord(ra_rand_masked_ds, dec_rand_masked_ds, frame='icrs')
350            ds2_rand = hp_object.interpolate_bilinear_skycoord(coords_rand, sdgm_high)
351            ds1_rand = hp_object.interpolate_bilinear_skycoord(coords_rand, sdgm_low)
352            
353            A2 = 2*np.pi*(1-np.cos(BinsRad[i+1]))
354            A1 = 2*np.pi*(1-np.cos(BinsRad[i]))
355
356            w_theta_vector[realisation, i] = np.mean((A2*ds2-A1*ds1)/(A2-A1)) - np.mean((A2*ds2_rand-A1*ds1_rand)/(A2-A1))
357
358        if Verbose: print('\tdone. time taken for realisation {}: {:.2e} s.'.format(realisation+1, time.perf_counter()-start_time_real))
359
360    #-----------------------------------------------------------------------------------------------
361
362    if Verbose: print('\ntotal time taken: {:.2e} s.'.format(time.perf_counter()-total_start_time))
363    
364    if ReturnSmoothedDict: 
365        return w_theta_vector, SmoothedFieldDict
366    else: 
367        return w_theta_vector

Returns a 'data vector' of the angular two-point cross-correlation function between multiple realisations of the given set of discrete tracers (MaskedTracerPosVectorRad) and a single realisation of the given continuous overdensity field (FieldSkymap) at the given angular distance scales (BinsRad). Please refer to notes to understand why this might be useful.

Parameters
  • BinsRad (list of numpy float array): array of angular distances (in radians) to compute the cross-correlation function at
  • MaskedTracerPosVectorRad (numpy float array of shape (n_realisations, n_tracer, 2)): array of sky locations for the first set of discrete tracers. For each data point in the array, the first (second) coordinate should be the declination (right ascension) in radians. Please ensure -np.pi/2 <= declination <= pi/2 and 0 <= right ascension <= 2*np.pi.
  • FieldSkymap (numpy float array): the healpy map of the continuous field. The values of the masked pixels, if any, should be set to hp.UNSEEN.
  • NR_ND (int): ratio of number of randoms to number of data points used in the 2PCF calculation to remove biases caused by the presence of an observational mask, by default 10. This is similar to the ratio of number of randoms to number data points used in the usual Landy-Szalay estimator of the 2PCF1. See notes for a more detailed explanation of this parameter and how to set it appropriately.
  • ReturnSmoothedDict (bool, optional): if set to True, the dictionary containing the continuous field masked within the observational footprint, and smoothed at the provided angular distance scales, will be returned along with the nearest-neighbour measurements, by default False.
  • Verbose (bool, optional): if set to True, the time taken to complete each step of the calculation will be printed, by default False.
Returns
  • w_theta_vector (numpy float array of shape (n_realisations, len(BinsRad)-1)): data vector containing the tracer-field two-point cross-correlation function for multiple realisations of the discrete tracer set evaluated at the desired distance bins. Note that the 2PCF can't be estimated at the last bin due to the nature of the algorithm (refer to notes for details).
  • SmoothedFieldDict (dict): dictionary containing the continuous field masked within the observational footprint and smoothed at the provided angular distance scales, returned only if ReturnSmoothedDict is True. For example, SmoothedFieldDict['0.215'] represents the continuous map smoothed at a scale of 0.215 radians.
Raises
  • ValueError: if declination of any of the tracer points is not in [-np.pi/2, np.pi/2].
  • ValueError: if right ascension of any of the tracer points is not in [0, 2*np.pi].
  • ValueError: if the given tracer points are not on a two-dimensional grid.
  • ValueError: if the NR_ND is not an integer.
See Also

kNNpy.Auxilliary.TPCF.TracerField2DA.CorrelationFunction: computes tracer-field two-point cross-correlations in 2D for a single realisation of the tracer set.
kNNpy.Auxilliary.TPCF.3DTPCF_Tracer-Field.CrossCorr2pt: computes tracer-field two-point cross-correlations in 3D.
kNNpy.kNN_2D_Ang.TracerFieldCross2DA_DataVector: computes a data vector of 2D angular tracer-field cross-correlations for multiple tracer realisations using the $k$NN formalism.

Notes

Please refer to the documentation of kNNpy.Auxilliary.TPCF.TracerFieldCross2DA.CorrelationFunction for important usage notes that also apply to this function and references.

References

  1. Kaustubh Rajesh Gupta, Spatial clustering of gravitational wave sources with k-nearest neighbour distributions, MS Thesis, Indian Institute of Science Education and Research Pune Digital Repository