Source code for CorrHOD.cutsky

import numpy as np
import logging
from pathlib import Path

from warnings import warn

from densitysplit.pipeline import DensitySplit

from pycorr import TwoPointCorrelationFunction, project_to_multipoles

from CorrHOD.cubic import CorrHOD_cubic
from CorrHOD.weights import n_z, w_fkp, get_quantiles_weight, sky_fraction

# Imports specific to packages versions
try:
    from densitysplit.utilities import sky_to_cartesian, cartesian_to_sky
except ImportError: # For main branch of densitysplit
    from densitysplit.utils import sky_to_cartesian, cartesian_to_sky

[docs]class CorrHOD_cutsky(CorrHOD_cubic): """ This class is used to compute the 2PCF and the autocorrelation and cross-correlation of the quantiles of the DensitySplit. It takes HOD parameters and a cosmology as input and uses AbacusHOD to generate a DESI cutsky mock. It then uses DensitySplit to compute the density field and the quantiles. Finally, it uses pycorr to compute the 2PCF and the autocorrelation and cross-correlation of the quantiles. If a cutsky file is to be provided, the cutsky must be in the form of a dictionary in sky coordinates. Every computation in this class is done in sky coordinates, except for the densitysplit. The conversion is done using the densitysplit package. Unless the name of the variable indicates it, the positions are in (ra, dec, comoving distance) coordinates. If the name of the variable contains 'sky', the positions are in (ra, dec, z) coordinates. """ # __init__ method is the same as in the CorrHOD_cubic class # The initialize_halos method is the same as in the CorrHOD_cubic class # The populate_halos method is the same as in the CorrHOD_cubic class # TODO (last) : Create the cutsky from the box --> Mockfactory package ! # TODO : Add a way to read the cutsky from a file # TODO : create_randoms method
[docs] def get_tracer_positions(self, return_cartesian:bool=False): """ Get the positions of the tracers (data and randoms) in cartesian coordinates. Note : For now, we assume that the RSD are already applied to the data Returns ------- data_positions : array_like The positions of the galaxies in cartesian coordinates. randoms_positions : array_like The positions of the randoms in cartesian coordinates. """ # Create these parameters for readability and make sure that we have the right dict passed try: data = self.cutsky_dict[self.tracer] randoms = self.randoms_dict[self.tracer] except: # Handle the case where we have set the dicts without using the set_cutsky method warn('The object is not a hod_dict. Trying to load it as a positional dataset.', UserWarning) data = self.cutsky_dict randoms = self.randoms_dict # Check that the data contains the right keys if not ( all([key in data.keys() for key in ['RA', 'DEC', 'Z']]) or all([key in randoms.keys() for key in ['RA', 'DEC', 'Z']]) ): raise ValueError('The object is not a hod_dict and does not contain the keys "RA", "DEC", "Z"') data_cd = self.cosmo.comoving_radial_distance(data['Z']) # Convert the z to comoving distance random_cd = self.cosmo.comoving_radial_distance(randoms['Z']) # Convert the z to comoving distance # Concatenate the positions with the comoving distance self.data_positions = np.c_[data['RA'], data['DEC'], data_cd] self.randoms_positions = np.c_[randoms['RA'], randoms['DEC'], random_cd] # Concatenate the positions in sky coordinates self.data_sky = np.c_[data['RA'], data['DEC'], data['Z']] self.randoms_sky = np.c_[randoms['RA'], randoms['DEC'], randoms['Z']] # Use densitysplit to convert to cartesian coordinates self.data_cartesian = sky_to_cartesian(self.data_sky, self.ds_cosmo) self.randoms_cartesian = sky_to_cartesian(self.randoms_sky, self.ds_cosmo) if return_cartesian: return self.data_cartesian, self.randoms_cartesian return self.data_positions, self.randoms_positions
[docs] def get_tracer_weights(self, edges:list=None, area:float=None, fsky:float=None, P0:float=7000): """ Compute the weights for the tracer and the randoms. Note : If neither `area` nor `fsky` are set, the sky fraction of the data will be computed, using the current data sky positions. Parameters ---------- edges: list, optional The edges of the bins used to compute the number density. If set to `None`, the edges are computed using Scott's rule. Defaults to `None`. area: float, optional The area of the survey in square degrees. Defaults to `None`. fsky: float, optional The fraction of the sky covered by the survey. Defaults to `None`. P0: float, optional The power spectrum normalization (TODO : Check this definition). Defaults to `7000` for the BGS. Returns ------- data_weights : array_like The weights of the galaxies. randoms_weights : array_like The weights of the randoms. """ # Get the sky fraction if it is not set if fsky is None and area is None: ra = self.randoms_sky[:,0] dec = self.randoms_sky[:,1] fsky = sky_fraction(ra, dec) z_data = self.data_positions[:,2] z_random = self.randoms_positions[:,2] # Compute the weights self.data_weights, self.randoms_weights = w_fkp(z_data, z_random, self.cosmo, edges=edges, area=area, fsky=fsky, P0=P0) if hasattr(self, 'quantiles'): # Compute the weights for the quantiles self.quantiles_weights = get_quantiles_weight(self.density, self.randoms_weights, nquantiles=len(self.quantiles)) return self.data_weights, self.randoms_weights, self.quantiles_weights return self.data_weights, self.randoms_weights
[docs] def compute_DensitySplit(self, smooth_radius: float = 10, cellsize: float = 5, nquantiles: int = 10, sampling: str = 'randoms', randoms=None, filter_shape: str = 'Gaussian', return_density: bool = True, nthread=16): """ Compute the density field and the quantiles using DensitySplit. (see https://github.com/epaillas/densitysplit for more details) Parameters ---------- smooth_radius : float, optional The radius of the Gaussian smoothing in Mpc/h. Defaults to 10. cellsize : float, optional The size of the cells in the mesh. Defaults to 10. nquantiles : int, optional The number of quantiles to compute. Defaults to 10. sampling : str, optional The sampling to use. Can be 'randoms' or 'data'. Defaults to 'randoms'. randoms : np.ndarray, optional The positions of the randoms. It is a (N,3) array of points in the range [0, boxsize] for each coordinate. If set to none, a the randoms positions of the data will be used. Defaults to None. filter_shape : str, optional The shape of the filter to use. Can be 'Gaussian' or 'TopHat'. Defaults to 'Gaussian'. return_density : bool, optional If True, the density field is returned. Defaults to True. nthread : int, optional The number of threads to use. Defaults to 16. Returns ------- quantiles : np.ndarray The quantiles. It is a (nquantiles,N,3) array. density : np.ndarray, optional The density field. It is a (N,3) array. """ logger = logging.getLogger('DensitySplit') #tmp # Get the positions of the galaxies if not hasattr(self, 'data_positions'): self.get_tracer_positions() # Set the weights of the galaxies to 1 if they are not set if not hasattr(self, 'data_weights'): self.data_weights = np.ones(len(self.data_positions)) if not hasattr(self, 'randoms_weights'): self.randoms_weights = np.ones(len(self.randoms_positions)) try: # Main branch logger.debug('Launched densitysplit on main branch') ds = DensitySplit(data_positions=self.data_cartesian, data_weights=self.data_weights, randoms_positions=self.randoms_cartesian, randoms_weights=self.randoms_weights) self.density = ds.get_density_mesh(smooth_radius=smooth_radius, cellsize=cellsize, sampling=sampling, filter_shape=filter_shape) except : #OpenMP branch (an error will be raised because the OpenMP branch used differently) logger.debug('Launched densitysplit on openmp branch') #tmp ds = DensitySplit(data_positions=self.data_cartesian, data_weights=self.data_weights, randoms_positions=self.randoms_cartesian, randoms_weights=self.randoms_weights, boxpad=1.1, cellsize=cellsize, nthreads=nthread) if sampling == 'randoms' and randoms is None: # Sample the positions on random points that we have to create in that branch logger.debug('No randoms provided, creating randoms') sampling_positions = self.randoms_cartesian elif sampling == 'randoms': # Sample the positions on the provided randoms sampling_positions = randoms elif sampling == 'data': # Sample the positions on the data positions sampling_positions = self.data_cartesian else: raise ValueError('The sampling parameter must be either "randoms" or "data"') self.density = ds.get_density_mesh(sampling_positions=sampling_positions, smoothing_radius=smooth_radius) # Temporary fix waiting for an update of the code : Remove the unphysical values (density under -1) self.density[self.density < -1] = -1 # Remove the outliers # Compute the quantiles in cartesian coordinates self.quantiles_cartesian = ds.get_quantiles(nquantiles=nquantiles) # Convert the quantiles to sky coordinates self.quantiles=[] self.quantiles_sky = [] for i in range(nquantiles): self.quantiles_sky.append(cartesian_to_sky(self.quantiles_cartesian[i], self.ds_cosmo)) # Save the quantiles in sky coordinates (for the n(z) computation) sky_quantile = cartesian_to_sky(self.quantiles_cartesian[i], self.ds_cosmo) # Convert the quantiles to sky coordinates sky_quantile[:,2] = self.cosmo.comoving_radial_distance(sky_quantile[:,2]) # Convert the z to comoving distance self.quantiles.append(sky_quantile) # Save the quantiles with the comoving distance if return_density: return self.quantiles, self.density return self.quantiles
[docs] def get_nz(self, edges:list=None, area:float=None, fsky:float=None): """ Computes the n(z) function of the data and the quantiles (if they exist). Note : If neither `area` nor `fsky` are set, the sky fraction of the data will be computed, using the current data sky positions. Parameters ---------- edges: list, optional The edges of the bins used to compute the number density. If set to `None`, the edges are computed using Scott's rule. Defaults to `None`. area: float, optional The area of the survey in square degrees. Defaults to `None`. fsky: float, optional The fraction of the sky covered by the survey. Defaults to `None`. Returns ------- nz_data : InterpolatedUnivariateSpline The n(z) function of the data. nz_randoms : InterpolatedUnivariateSpline The n(z) function of the randoms. nz_functions : list, optional The n(z) function for each quantile. It is a list of InterpolatedUnivariateSpline objects. """ # Get the sky fraction if it is not set if fsky is None and area is None: ra = self.randoms_sky[:,0] dec = self.randoms_sky[:,1] fsky = sky_fraction(ra, dec) # Check that the mean n(z) is approx. the same for each quantile if hasattr(self, 'quantiles'): nquantiles = len(self.quantiles) self.nz_functions = [n_z(self.quantiles_sky[i][:,2], self.cosmo, edges=edges, area=area, fsky=fsky) for i in range(nquantiles)] # Compute the n(z) functions # Get the positions of the galaxies if not hasattr(self, 'data_positions'): self.get_tracer_positions() self.nz_data = n_z(self.data_sky[:,2], self.cosmo, edges=edges, area=area, fsky=fsky) self.nz_randoms = n_z(self.randoms_sky[:,2], self.cosmo, edges=edges, area=area, fsky=fsky) if hasattr(self, 'nz_functions') and hasattr(self, 'nz_data'): return self.nz_data, self.nz_randoms, self.nz_functions return self.nz_data, self.nz_randoms
[docs] def downsample_data(self, frac: float=None, new_n: float=None, npoints: int=None): """ Downsamples the data to a given number density. The number of points in the randoms and the quantiles will be adjusted accordingly. (i.e. if the number density of the data is 10 times smaller, the number of points in the randoms and the quantiles will be 10 times smaller, while keeping the proportionality to the data) Since the number density varies with the redshift, the downsampling to the new number density is done by taking the mean n(z) of the data as reference. This should have n(z) remaining approx. the same. (TODO : Test this ?) Parameters ---------- new_n : float The wanted number density of the data after downsampling. The number density of the randoms and the quantiles will be adjusted accordingly. Returns ------- data_positions : np.ndarray The positions of the data after downsampling. randoms_positions : np.ndarray The positions of the randoms after downsampling. quantiles : np.ndarray, optional The quantiles after downsampling. """ logger = logging.getLogger('CorrHOD') # Log some info just in case # First, get the n(z) of the data and quantiles if they exist self.get_nz() mean_n = np.mean(self.nz_data(self.data_sky[:,2])) # Get the mean n(z) of the data N = len(self.data_positions) # Get the number of galaxies in the data # First, check that only one of the three parameters is set if np.sum([frac is not None, new_n is not None, npoints is not None]) != 1: raise ValueError('Only one of the parameters frac, new_n and npoints must be set.') # Then, get the other parameters from the one that is set if frac is not None: npoints = int(frac * N) new_n = mean_n * frac if npoints is not None: frac = npoints / N new_n = mean_n * frac if new_n is not None: frac = new_n / mean_n npoints = int(frac * N) # Check that the new number density is not too small if mean_n < new_n or new_n is None: logger.warning(f'Data not downsampled due to number density {new_n:.2e} ({npoints} points) too small or None') if not hasattr(self, 'quantiles'): return self.data_positions, self.randoms_positions else: return self.data_positions, self.randoms_positions, self.quantiles # First, downsample the data wanted_number = int(N * new_n / mean_n) # Get the wanted number of galaxies after downsampling sample_indices = np.random.choice(N, size=wanted_number, replace=False) # Get the indices of the galaxies to keep self.data_positions = self.data_positions[sample_indices] # Keep only the selected galaxies self.data_weights = self.data_weights[sample_indices] # Keep only the selected weights self.data_sky = self.data_sky[sample_indices] # Keep only the selected galaxies in sky coordinates self.data_cartesian = self.data_cartesian[sample_indices] # Keep only the selected galaxies in cartesian coordinates logger.info(f'Downsampling the data to a number density of {new_n:.2e} h^3/Mpc^3: {len(self.data_positions)} galaxies remaining from {N} galaxies') # Then, downsample the randoms N = len(self.randoms_positions) # Get the number of galaxies in the randoms wanted_number = int(N * new_n / mean_n) # Get the wanted number of randoms after downsampling sample_indices = np.random.choice(N, size=wanted_number, replace=False) # Get the indices of the randoms to keep self.randoms_positions = self.randoms_positions[sample_indices] # Keep only the selected randoms self.randoms_weights = self.randoms_weights[sample_indices] # Keep only the selected weights self.randoms_sky = self.randoms_sky[sample_indices] # Keep only the selected randoms in sky coordinates self.randoms_cartesian = self.randoms_cartesian[sample_indices] # Keep only the selected randoms in cartesian coordinates logger.info(f'Downsampling the randoms : {len(self.randoms_positions)} randoms remaining from {N} randoms') # If the quantiles have been computed, downsample them too if not hasattr(self, 'quantiles'): return self.data_positions, self.randoms_positions # Finally, downsample the quantiles nquantiles = len(self.quantiles) wanted_number = int(len(self.randoms_positions)/nquantiles) # Get the wanted number of quantiles after downsampling N = np.min([len(self.quantiles[i]) for i in range(nquantiles)]) # The number of points CAN VARY (by 1 or 2) between the quantiles, so we take the smallest one for i in range(nquantiles): sample_indices = np.random.choice(N, size=wanted_number, replace=False) # Get the indices of the quantiles to keep self.quantiles[i] = self.quantiles[i][sample_indices] # Keep only the selected quantiles self.quantiles_weights[i] = self.quantiles_weights[i][sample_indices] # Keep only the selected weights self.quantiles_sky[i] = self.quantiles_sky[i][sample_indices] # Keep only the selected quantiles in sky coordinates self.quantiles_cartesian[i] = self.quantiles_cartesian[i][sample_indices] # Keep only the selected quantiles in cartesian coordinates logger.info(f'Downsampling the quantiles: {len(self.quantiles[0])} points remaining from {N} points') return self.data_positions, self.randoms_positions, self.quantiles
[docs] def compute_auto_corr(self, quantile:int, mode:str = 'smu', edges:list = [np.linspace(0.1, 200, 50), np.linspace(-1, 1, 60)], mpicomm = None, mpiroot = None, nthread:int = 16): """ Compute the auto-correlation of a quantile. The result will also be saved in the class, as `self.CF['Auto'][f'DS{quantile}']` Parameters ---------- quantile : int Index of the quantile to compute. It must be between 0 and nquantiles-1. mode : str, optional The mode to use for the 2PCF. Defaults to 'smu'. See pycorr for more details . edges : list, optional The edges to use for the 2PCF. Defaults to [np.linspace(0.1, 200, 50), np.linspace(-1, 1, 60)]. See pycorr for more details. mpicomm : _type_, optional The MPI communicator. Defaults to None. mpiroot : _type_, optional The MPI root. Defaults to None. nthread : int, optional The number of threads to use. Defaults to 16. Returns ------- xi_quantile : pycorr.TwoPointCorrelationFunction The auto-correlation of the quantile. """ if not hasattr(self, 'quantiles'): raise ValueError('The quantiles have not been computed yet. Run compute_DensitySplit first.') # Get the positions of the galaxies if not hasattr(self, 'randoms_positions'): self.get_tracer_positions() # Get the positions of the points in the quantile quantile_positions = self.quantiles[quantile] # An array of 3 columns (ra, dec, z) # Set the weights of the galaxies to 1 if they are not set if not hasattr(self, 'randoms_weights'): self.randoms_weights = np.ones(len(self.randoms_positions)) if not hasattr(self, 'quantiles_weights'): quantile_weights = np.ones(len(quantile_positions)) else: quantile_weights = self.quantiles_weights[quantile] # Initialize the dictionary for the correlations if not hasattr(self, 'CF'): self.CF = {} if not (self.los in self.CF.keys()): self.CF[self.los] = {} if not ('Auto' in self.CF[self.los].keys()): self.CF[self.los]['Auto'] = {} # Initialize the dictionary for the pycorr objects if not hasattr(self, 'xi'): self.xi = {} if not (self.los in self.xi.keys()): self.xi[self.los] = {} if not ('Auto' in self.xi[self.los].keys()): self.xi[self.los]['Auto'] = {} # Compute the 2pcf xi_quantile = TwoPointCorrelationFunction(mode, edges, data_positions1=quantile_positions.T, # Note the transpose to get the right shape randoms_positions1=self.randoms_positions.T, data_weights1=quantile_weights, randoms_weights1=self.randoms_weights, position_type='rdd', mpicomm=mpicomm, mpiroot=mpiroot, num_threads=nthread) # Add the 2pcf to the dictionary if not ('s' in self.CF[self.los]): # Note that the s is the same for all the lines of sight as long as we give the same edges to the 2PCF function s, poles = project_to_multipoles(xi_quantile) self.CF[self.los]['s'] = s else: poles = project_to_multipoles(xi_quantile, return_sep=False) self.CF[self.los]['Auto'][f'DS{quantile}'] = poles self.xi[self.los]['Auto'][f'DS{quantile}'] = xi_quantile return xi_quantile
[docs] def compute_cross_corr(self, quantile:int, mode:str = 'smu', edges:list = [np.linspace(0.1, 200, 50), np.linspace(-1, 1, 60)], mpicomm = None, mpiroot = None, nthread:int = 16): """ Compute the cross-correlation of a quantile with the galaxies. The result will also be saved in the class, as `self.CF['Cross'][f'DS{quantile}']` Parameters ---------- quantile : int Index of the quantile to compute. It must be between 0 and nquantiles-1. mode : str, optional The mode to use for the 2PCF. Defaults to 'smu'. See pycorr for more details . edges : list, optional The edges to use for the 2PCF. Defaults to [np.linspace(0.1, 200, 50), np.linspace(-1, 1, 60)]. See pycorr for more details. mpicomm : _type_, optional The MPI communicator. Defaults to None. mpiroot : _type_, optional The MPI root. Defaults to None. nthread : int, optional The number of threads to use. Defaults to 16. Returns ------- xi_quantile : pycorr.TwoPointCorrelationFunction The cross-correlation of the quantile. """ if not hasattr(self, 'quantiles'): raise ValueError('The quantiles have not been computed yet. Run compute_DensitySplit first.') # Get the positions of the points in the quantile quantile_positions = self.quantiles[quantile] # An array of 3 columns (x,y,z) # Get the positions of the galaxies if not hasattr(self, 'data_positions'): self.get_tracer_positions() # Set the weights of the galaxies to 1 if they are not set if not hasattr(self, 'data_weights'): self.data_weights = np.ones(len(self.data_positions)) if not hasattr(self, 'randoms_weights'): self.randoms_weights = np.ones(len(self.randoms_positions)) if not hasattr(self, 'quantiles_weights'): quantile_weights = np.ones(len(quantile_positions)) else: quantile_weights = self.quantiles_weights[quantile] # Initialize the dictionary for the correlations if not hasattr(self, 'CF'): self.CF = {} if not (self.los in self.CF.keys()): self.CF[self.los] = {} if not ('Cross' in self.CF[self.los].keys()): self.CF[self.los]['Cross'] = {} # Initialize the dictionary for the pycorr objects if not hasattr(self, 'xi'): self.xi = {} if not (self.los in self.xi.keys()): self.xi[self.los] = {} if not ('Cross' in self.xi[self.los].keys()): self.xi[self.los]['Cross'] = {} # Compute the 2pcf xi_quantile = TwoPointCorrelationFunction(mode, edges, data_positions1=quantile_positions.T, # Note the transpose to get the right shape data_positions2=self.data_positions.T, randoms_positions1=self.randoms_positions.T, randoms_positions2=self.randoms_positions.T, data_weights1=quantile_weights, data_weights2=self.data_weights, randoms_weights1=self.randoms_weights, randoms_weights2=self.randoms_weights, position_type='rdd', mpicomm=mpicomm, mpiroot=mpiroot, num_threads=nthread) # Add the 2pcf to the dictionary if not ('s' in self.CF[self.los]): # Note that the s is the same for all the lines of sight as long as we give the same edges to the 2PCF function s, poles = project_to_multipoles(xi_quantile) self.CF[self.los]['s'] = s else: poles = project_to_multipoles(xi_quantile, return_sep=False) self.CF[self.los]['Cross'][f'DS{quantile}'] = poles self.xi[self.los]['Cross'][f'DS{quantile}'] = xi_quantile return xi_quantile
[docs] def compute_2pcf(self, mode:str = 'smu', edges:list = [np.linspace(0.1, 200, 50), np.linspace(-1, 1, 60)], mpicomm = None, mpiroot = None, nthread:int = 16): """ Compute the 2PCF of the galaxies. The result will also be saved in the class, as `self.CF['2PCF']` Parameters ---------- mode : str, optional The mode to use for the 2PCF. Defaults to 'smu'. See pycorr for more details . edges : list, optional The edges to use for the 2PCF. Defaults to [np.linspace(0.1, 200, 50), np.linspace(-1, 1, 60)]. See pycorr for more details. mpicomm : _type_, optional The MPI communicator. Defaults to None. mpiroot : _type_, optional The MPI root. Defaults to None. nthread : int, optional The number of threads to use. Defaults to 16. Returns ------- xi : pycorr.TwoPointCorrelationFunction The 2PCF of the galaxies. """ # Get the positions of the galaxies if not hasattr(self, 'data_positions'): self.get_tracer_positions() # Set the weights of the galaxies to 1 if they are not set if not hasattr(self, 'data_weights'): self.data_weights = np.ones(len(self.data_positions)) if not hasattr(self, 'randoms_weights'): self.randoms_weights = np.ones(len(self.randoms_positions)) # Initialize the dictionary for the correlations if not hasattr(self, 'CF'): self.CF = {} if not (self.los in self.CF.keys()): self.CF[self.los] = {} if not ('2PCF' in self.CF[self.los].keys()): self.CF[self.los]['2PCF'] = {} # Initialize the dictionary for the pycorr objects if not hasattr(self, 'xi'): self.xi = {} if not (self.los in self.xi.keys()): self.xi[self.los] = {} if not ('Auto' in self.xi[self.los].keys()): self.xi[self.los]['2PCF'] = {} # Compute the 2pcf xi = TwoPointCorrelationFunction(mode, edges, data_positions1 = self.data_positions.T, # Note the transpose to get the right shape randoms_positions1 = self.randoms_positions.T, data_weights1=self.data_weights, randoms_weights1=self.randoms_weights, position_type = 'rdd', mpicomm = mpicomm, mpiroot = mpiroot, num_threads = nthread) # Add the 2pcf to the dictionary if not ('s' in self.CF[self.los]): # Note that the s is the same for all the lines of sight as long as we give the same edges to the 2PCF function s, poles = project_to_multipoles(xi) self.CF[self.los]['s'] = s else: poles = project_to_multipoles(xi, return_sep=False) self.CF[self.los]['2PCF'] = poles self.xi[self.los]['2PCF'] = xi return xi
# Average_cf method should be the same as in the CorrHOD_cubic class # Save method should be the same as in the CorrHOD_cubic class
[docs] def save(self, hod_indice: int = 0, path: str = None, save_HOD: bool = True, save_pos: bool = True, save_density: bool = True, save_quantiles: bool = True, save_CF: bool = True, save_xi: bool = True, los: str = 'average', save_all: bool = False): if path is None: output_dir = Path(self.sim_params['output_dir']) else : output_dir = Path(path) sim_name = self.sim_params['sim_name'] # Get the cosmo and phase from sim_name (Naming convention has to end by '_c{cosmo}_p{phase}' !) cosmo = sim_name.split('_')[-2].split('c')[-1] # Get the cosmology number by splitting the name of the simulation phase = sim_name.split('_')[-1].split('ph')[-1] # Get the phase number by splitting the name of the simulation # Get the HOD indice in the right format (same as the cosmology and phase) hod_indice = f'{hod_indice:03d}' cutsky_name = f'pos_hod{hod_indice}_c{cosmo}_ph{phase}.npy' # The cubic dictionary does not depend on the line of sight either if save_pos or (save_all and hasattr(self, 'cubic_dict')): # Pass if the cubic dictionary has not been computed yet if not hasattr(self, 'cubic_dict'): warn('The cubic dictionary has not been computed yet. Run populate_halos first.', UserWarning) pass path = output_dir path.mkdir(parents=True, exist_ok=True) # Create the directory if it does not exist np.save(path / cutsky_name, self.cubic_dict) if save_all: save_HOD = save_density = save_quantiles = save_CF = save_xi = True # Everything else is the same as in the CorrHOD_cubic class, so we can use the super method super().save(hod_indice, path=path, save_HOD=save_HOD, save_pos=False, save_density=save_density, save_quantiles=save_quantiles, save_CF=save_CF, save_xi=save_xi, los=los, save_all=False)
# TODO : Run_all # To convert the cutsky to cartesian coordinates --> Use the densitysplit package, it's already there !