def get_handles_from_paths( self, path_to_snap: str, path_to_catalogue: str, mask_radius_r500: float = 10 ) -> tuple: """ All quantities in the VR file are physical. All quantities in the Swiftsimio file are comoving. Convert them upon use. Args: path_to_snap: path_to_catalogue: mask_radius_r500: Returns: TODO: """ # Read in halo properties vr_handle = velociraptor.load(path_to_catalogue, disregard_units=True) # Try to import r500 from the catalogue. # If not there (and needs to be computed), assume 1 Mpc for the spatial mask. try: r500 = vr_handle.spherical_overdensities.r_500_rhocrit[0].to('Mpc') / vr_handle.a except Exception as err: r500 = unyt_quantity(3, 'Mpc') / vr_handle.a if xlargs.debug: print(err, "Setting r500 = 3. Mpc. / scale_factor", sep='\n') xcminpot = vr_handle.positions.xcminpot[0].to('Mpc') / vr_handle.a ycminpot = vr_handle.positions.ycminpot[0].to('Mpc') / vr_handle.a zcminpot = vr_handle.positions.zcminpot[0].to('Mpc') / vr_handle.a # Apply spatial mask to particles. SWIFTsimIO needs comoving coordinates # to filter particle coordinates, while VR outputs are in physical units. # Convert the region bounds to comoving, but keep the CoP and Rcrit in # physical units for later use. mask = swiftsimio.mask(path_to_snap) mask_radius = mask_radius_r500 * r500 region = [ [xcminpot - mask_radius, xcminpot + mask_radius], [ycminpot - mask_radius, ycminpot + mask_radius], [zcminpot - mask_radius, zcminpot + mask_radius] ] mask.constrain_spatial(region) sw_handle = swiftsimio.load(path_to_snap, mask=mask) if len(sw_handle.gas.coordinates) == 0: raise ValueError(( "The spatial masking of the snapshot returned 0 particles. " "Check whether an appropriate aperture is selected and if " "the physical/comoving units match." )) elif xlargs.debug: print(( f"[{self.__class__.__name__}] Particles in snap file:\n\t| " f"{sw_handle.metadata.n_gas:11d} gas | " f"{sw_handle.metadata.n_dark_matter:11d} dark_matter | " f"{sw_handle.metadata.n_stars:11d} stars | " f"{sw_handle.metadata.n_black_holes:11d} black_holes | " )) # If the mask overlaps with the box boundaries, wrap coordinates. boxsize = sw_handle.metadata.boxsize centre_coordinates = unyt_array([xcminpot, ycminpot, zcminpot], xcminpot.units) sw_handle.gas.coordinates = self.wrap_coordinates( sw_handle.gas.coordinates, centre_coordinates, boxsize ) sw_handle.dark_matter.coordinates = self.wrap_coordinates( sw_handle.dark_matter.coordinates, centre_coordinates, boxsize ) if sw_handle.metadata.n_stars > 0: sw_handle.stars.coordinates = self.wrap_coordinates( sw_handle.stars.coordinates, centre_coordinates, boxsize ) if sw_handle.metadata.n_black_holes > 0: sw_handle.black_holes.coordinates = self.wrap_coordinates( sw_handle.black_holes.coordinates, centre_coordinates, boxsize ) # Compute radial distances sw_handle.gas.radial_distances = self.get_radial_distance( sw_handle.gas.coordinates, centre_coordinates ) sw_handle.dark_matter.radial_distances = self.get_radial_distance( sw_handle.dark_matter.coordinates, centre_coordinates ) if sw_handle.metadata.n_stars > 0: sw_handle.stars.radial_distances = self.get_radial_distance( sw_handle.stars.coordinates, centre_coordinates ) if sw_handle.metadata.n_black_holes > 0: sw_handle.black_holes.radial_distances = self.get_radial_distance( sw_handle.black_holes.coordinates, centre_coordinates ) if xlargs.debug: print(( f"[{self.__class__.__name__}] Particles in mask:\n\t| " f"{sw_handle.gas.coordinates.shape[0]:11d} gas | " f"{sw_handle.dark_matter.coordinates.shape[0]:11d} dark_matter | " f"{sw_handle.stars.coordinates.shape[0] if sw_handle.metadata.n_stars > 0 else 0:11d} stars | " f"{sw_handle.black_holes.coordinates.shape[0] if sw_handle.metadata.n_black_holes > 0 else 0:11d} black_holes | " )) return sw_handle, vr_handle
dimension=3, ) base_coordinates = np.arange(0.0, 1.0, 1.0 / NUMBER_OF_PARTICLES) dataset.gas.coordinates = unyt.unyt_array( [x.flatten() for x in np.meshgrid(*[base_coordinates] * 3)], "cm").T dataset.gas.generate_smoothing_lengths(boxsize=dataset.box_size, dimension=3) base_velocities = np.zeros_like(base_coordinates) dataset.gas.velocities = unyt.unyt_array( [x.flatten() for x in np.meshgrid(*[base_velocities] * 3)], "cm/s").T # Set the particle with the highest mass to be in the centre of # the volume for easier plotting later special_particle = (np.linalg.norm(dataset.gas.coordinates - unyt.unyt_quantity(0.5, "cm"), axis=1)).argmin() base_masses = np.ones(TOTAL_NUMBER_OF_PARTICLES, dtype=np.float32) base_masses[special_particle] = np.float32(PARTICLE_OVER_MASS_FACTOR) dataset.gas.masses = unyt.unyt_array(base_masses, "g") # Set internal energy to be consistent with a CFL time-step of the # required length internal_energy = (dataset.gas.smoothing_length[0] * 0.1 / TIME_STEP)**2 / (5 / 3 * (5 / 3 - 1)) internal_energies = (np.ones(TOTAL_NUMBER_OF_PARTICLES, dtype=np.float32) * internal_energy) dataset.gas.internal_energy = unyt.unyt_array(internal_energies, "cm**2 / s**2")
color="white", fill=False, linestyle='-') ax.add_artist(circle_10r200) ax.text(x[i], y[i] + 1.05 * 10 * R200c[i], f"{i}", color="white", ha="center", va="bottom") fig.savefig(f"outfiles/volume_DMmap.png") plt.close(fig) for i in range(3): print(f"Rendering halo {i}...") xCen = unyt.unyt_quantity(x[i], unyt.Mpc) yCen = unyt.unyt_quantity(y[i], unyt.Mpc) zCen = unyt.unyt_quantity(z[i], unyt.Mpc) size = unyt.unyt_quantity(out_to_radius * R200c[i], unyt.Mpc) mask = sw.mask(snapFile) region = [[xCen - size, xCen + size], [yCen - size, yCen + size], [zCen - size, zCen + size]] mask.constrain_spatial(region) # Load data using mask data = sw.load(snapFile, mask=mask) dm_mass = dm_render(data, region=(region[0] + region[1])) # Make figure fig, ax = plt.subplots(figsize=(8, 8), dpi=1024 // 8) fig.subplots_adjust(0, 0, 1, 1)
def profile_3d_single_halo( path_to_snap: str, path_to_catalogue: str, weights: str, hse_dataset: pd.Series = None, ) -> tuple: # Read in halo properties with h5.File(path_to_catalogue, 'r') as h5file: scale_factor = float(h5file['/SimulationInfo'].attrs['ScaleFactor']) M200c = unyt.unyt_quantity(h5file['/Mass_200crit'][0] * 1.e10, unyt.Solar_Mass) M500c = unyt.unyt_quantity(h5file['/SO_Mass_500_rhocrit'][0] * 1.e10, unyt.Solar_Mass) R200c = unyt.unyt_quantity(h5file['/R_200crit'][0], unyt.Mpc) / scale_factor R500c = unyt.unyt_quantity(h5file['/SO_R_500_rhocrit'][0], unyt.Mpc) / scale_factor XPotMin = unyt.unyt_quantity(h5file['/Xcminpot'][0], unyt.Mpc) / scale_factor YPotMin = unyt.unyt_quantity(h5file['/Ycminpot'][0], unyt.Mpc) / scale_factor ZPotMin = unyt.unyt_quantity(h5file['/Zcminpot'][0], unyt.Mpc) / scale_factor # If no custom aperture, select r500c as default if hse_dataset is not None: assert R500c.units == hse_dataset["R500hse"].units assert M500c.units == hse_dataset["M500hse"].units R500c = hse_dataset["R500hse"] M500c = hse_dataset["M500hse"] # Read in gas particles mask = sw.mask(path_to_snap, spatial_only=False) region = [[ XPotMin - radius_bounds[1] * R500c, XPotMin + radius_bounds[1] * R500c ], [ YPotMin - radius_bounds[1] * R500c, YPotMin + radius_bounds[1] * R500c ], [ ZPotMin - radius_bounds[1] * R500c, ZPotMin + radius_bounds[1] * R500c ]] mask.constrain_spatial(region) mask.constrain_mask("gas", "temperatures", Tcut_halogas * mask.units.temperature, 1.e12 * mask.units.temperature) data = sw.load(path_to_snap, mask=mask) # Convert datasets to physical quantities R500c *= scale_factor XPotMin *= scale_factor YPotMin *= scale_factor ZPotMin *= scale_factor data.gas.coordinates.convert_to_physical() data.gas.masses.convert_to_physical() data.gas.temperatures.convert_to_physical() data.gas.densities.convert_to_physical() data.gas.pressures.convert_to_physical() data.gas.entropies.convert_to_physical() data.dark_matter.masses.convert_to_physical() # Select hot gas within sphere posGas = data.gas.coordinates deltaX = posGas[:, 0] - XPotMin deltaY = posGas[:, 1] - YPotMin deltaZ = posGas[:, 2] - ZPotMin deltaR = np.sqrt(deltaX**2 + deltaY**2 + deltaZ**2) # Calculate particle mass and rho_crit unitLength = data.metadata.units.length unitMass = data.metadata.units.mass rho_crit = unyt.unyt_quantity( data.metadata.cosmology_raw['Critical density [internal units]'] / scale_factor**3, unitMass / unitLength**3) dm_masses = data.dark_matter.masses.to('Msun') zoom_mass_resolution = dm_masses[0] # Since useful for different applications, attach datasets data.gas.mass_weighted_temperatures = data.gas.masses * data.gas.temperatures # Rescale profiles to r500c radial_distance = deltaR / R500c assert radial_distance.units == unyt.dimensionless # Compute convergence radius conv_radius = convergence_radius(deltaR, data.gas.masses.to('Msun'), rho_crit.to('Msun/Mpc**3')) / R500c # Construct bins for mass-weighted quantities and retrieve bin_edges lbins = np.logspace(np.log10(radius_bounds[0]), np.log10(radius_bounds[1]), bins) * radial_distance.units mass_weights, bin_edges = histogram_unyt(radial_distance, bins=lbins, weights=data.gas.masses) # Replace zeros with Nans mass_weights[mass_weights == 0] = np.nan bin_centre = np.sqrt(bin_edges[1:] * bin_edges[:-1]) # Allocate weights if weights.lower() == 'gas_mass': hist = mass_weights / M500c.to(mass_weights.units) ylabel = r'$M(dR) / M_{500{\rm crit}}$' elif weights.lower() == 'gas_mass_cumulative': hist = cumsum_unyt(mass_weights) / M500c.to(mass_weights.units) ylabel = r'$M(<R) / M_{500{\rm crit}}$' elif weights.lower() == 'gas_density': hist, _ = histogram_unyt(radial_distance, bins=lbins, weights=data.gas.densities) hist /= rho_crit.to(hist.units) hist *= bin_centre**2 ylabel = r'$(\rho_{\rm gas}/\rho_{\rm crit})\ (R/R_{500{\rm crit}})^3 $' elif weights.lower() == 'mass_weighted_temps': weights_field = data.gas.mass_weighted_temperatures hist, _ = histogram_unyt(radial_distance, bins=lbins, weights=weights_field) hist /= mass_weights if sampling_method.lower() == 'no_binning': bin_centre = radial_distance hist = data.gas.temperatures ylabel = r'$T$ [K]' elif weights.lower() == 'mass_weighted_temps_kev': weights_field = data.gas.mass_weighted_temperatures hist, _ = histogram_unyt(radial_distance, bins=lbins, weights=weights_field) hist /= mass_weights hist = (hist * unyt.boltzmann_constant).to('keV') # Make dimensionless, divide by (k_B T_500crit) # unyt.G.in_units('Mpc*(km/s)**2/(1e10*Msun)') norm = unyt.G * mean_molecular_weight * M500c * unyt.mass_proton / 2 / R500c norm = norm.to('keV') hist /= norm ylabel = r'$(k_B T/k_B T_{500{\rm crit}})$' elif weights.lower() == 'entropy': if sampling_method.lower() == 'shell_density': volume_shell = (4. * np.pi / 3.) * (R500c**3) * ( (bin_edges[1:])**3 - (bin_edges[:-1])**3) density_gas = mass_weights / volume_shell mean_density_R500c = (3 * M500c * obs.cosmic_fbary / (4 * np.pi * R500c**3)).to(density_gas.units) kBT, _ = histogram_unyt( radial_distance, bins=lbins, weights=data.gas.mass_weighted_temperatures) kBT *= unyt.boltzmann_constant kBT /= mass_weights kBT = kBT.to('keV') kBT_500crit = unyt.G * mean_molecular_weight * M500c * unyt.mass_proton / 2 / R500c kBT_500crit = kBT_500crit.to(kBT.units) # Note: the ratio of densities is the same as ratio of electron number densities hist = kBT / kBT_500crit * (mean_density_R500c / density_gas)**(2 / 3) elif sampling_method.lower() == 'particle_density': n_e = data.gas.densities ne_500crit = 3 * M500c * obs.cosmic_fbary / (4 * np.pi * R500c**3) kBT = unyt.boltzmann_constant * data.gas.mass_weighted_temperatures kBT_500crit = unyt.G * mean_molecular_weight * M500c * unyt.mass_proton / 2 / R500c weights_field = kBT / kBT_500crit * (ne_500crit / n_e)**(2 / 3) hist, _ = histogram_unyt(radial_distance, bins=lbins, weights=weights_field) hist /= mass_weights elif sampling_method.lower() == 'no_binning': n_e = data.gas.densities ne_500crit = 3 * M500c * obs.cosmic_fbary / (4 * np.pi * R500c**3) kBT = unyt.boltzmann_constant * data.gas.temperatures kBT_500crit = unyt.G * mean_molecular_weight * M500c * unyt.mass_proton / 2 / R500c weights_field = kBT / kBT_500crit * (ne_500crit / n_e)**(2 / 3) bin_centre = radial_distance hist = weights_field ylabel = r'$K/K_{500{\rm crit}}$' elif weights.lower() == 'entropy_physical': if sampling_method.lower() == 'shell_density': volume_shell = (4. * np.pi / 3.) * (R500c**3) * ( (bin_edges[1:])**3 - (bin_edges[:-1])**3) density_gas = mass_weights / volume_shell number_density_gas = density_gas / (mean_molecular_weight * unyt.mass_proton) number_density_gas = number_density_gas.to('1/cm**3') kBT, _ = histogram_unyt( radial_distance, bins=lbins, weights=data.gas.mass_weighted_temperatures) kBT *= unyt.boltzmann_constant kBT /= mass_weights kBT = kBT.to('keV') # Note: the ratio of densities is the same as ratio of electron number densities hist = kBT / number_density_gas**(2 / 3) hist = hist.to('keV*cm**2') elif sampling_method.lower() == 'particle_density': number_density_gas = data.gas.densities / (mean_molecular_weight * unyt.mass_proton) number_density_gas = number_density_gas.to('1/cm**3') kBT = unyt.boltzmann_constant * data.gas.mass_weighted_temperatures weights_field = kBT / number_density_gas**(2 / 3) hist, _ = histogram_unyt(radial_distance, bins=lbins, weights=weights_field) hist /= mass_weights hist = hist.to('keV*cm**2') elif sampling_method.lower() == 'no_binning': number_density_gas = data.gas.densities / (mean_molecular_weight * unyt.mass_proton) number_density_gas = number_density_gas.to('1/cm**3') kBT = unyt.boltzmann_constant * data.gas.temperatures weights_field = kBT / number_density_gas**(2 / 3) bin_centre = radial_distance hist = weights_field ylabel = r'$K$ [keV cm$^2$]' elif weights.lower() == 'pressure': if sampling_method.lower() == 'shell_density': volume_shell = (4. * np.pi / 3.) * (R500c**3) * ( (bin_edges[1:])**3 - (bin_edges[:-1])**3) density_gas = mass_weights / volume_shell number_density_gas = density_gas / (mean_molecular_weight * unyt.mass_proton) number_density_gas = number_density_gas.to('1/cm**3') kBT, _ = histogram_unyt( radial_distance, bins=lbins, weights=data.gas.mass_weighted_temperatures) kBT *= unyt.boltzmann_constant kBT /= mass_weights kBT = kBT.to('keV') # Note: the ratio of densities is the same as ratio of electron number densities hist = kBT * number_density_gas hist = hist.to('keV/cm**3') elif sampling_method.lower() == 'particle_density': weights_field = data.gas.pressures * data.gas.masses hist, _ = histogram_unyt(radial_distance, bins=lbins, weights=weights_field) hist /= mass_weights # Make dimensionless, divide by P_500crit norm = 500 * obs.cosmic_fbary * rho_crit * unyt.G * M500c / 2 / R500c hist /= norm.to(hist.units) hist *= bin_centre**3 ylabel = r'$(P/P_{500{\rm crit}})\ (R/R_{500{\rm crit}})^3 $' else: raise ValueError(f"Unrecognized weighting field: {weights}.") return bin_centre, hist, ylabel, conv_radius, M500c, R500c
def process_single_halo( self, zoom_obj: Zoom = None, path_to_snap: str = None, path_to_catalogue: str = None, mask_radius: Tuple[float, str] = (6, 'r500'), map_centre: Union[str, list, np.ndarray] = 'vr_centre_of_potential', temperature_range: Optional[tuple] = None, depth_offset: Optional[float] = None, return_type: Union[type, str] = 'class', inscribe_mask: bool = False, ): sw_data, vr_data = self.get_handles_from_zoom( zoom_obj, path_to_snap, path_to_catalogue, mask_radius_r500=15, ) map_centres_allowed = [ 'vr_centre_of_potential' ] if type(map_centre) is str and map_centre.lower() not in map_centres_allowed: raise AttributeError(( f"String-commands for `map_centre` only support " f"`vr_centre_of_potential`. Got {map_centre} instead." )) elif (type(map_centre) is list or type(map_centre) is np.ndarray) and len(map_centre) != 3: raise AttributeError(( f"List-commands for `map_centre` only support " f"length-3 lists. Got {map_centre} " f"(length {len(map_centre)}) instead." )) self.map_centre = map_centre centre_of_potential = [ vr_data.positions.xcminpot[0].to('Mpc') / vr_data.a, vr_data.positions.ycminpot[0].to('Mpc') / vr_data.a, vr_data.positions.zcminpot[0].to('Mpc') / vr_data.a ] if self.map_centre == 'vr_centre_of_potential': _xCen = centre_of_potential[0] _yCen = centre_of_potential[1] _zCen = centre_of_potential[2] elif type(self.map_centre) is list or type(self.map_centre) is np.ndarray: _xCen = self.map_centre[0] * Mpc / vr_data.a _yCen = self.map_centre[1] * Mpc / vr_data.a _zCen = self.map_centre[2] * Mpc / vr_data.a if xlargs.debug: print(f"Centre of potential: {[float(f'{i.v:.3f}') for i in centre_of_potential]} Mpc") print(f"Map centre: {[float(f'{i.v:.3f}') for i in [_xCen, _yCen, _zCen]]} Mpc") self.depth = _zCen / sw_data.metadata.boxsize[0] if depth_offset is not None: self.depth += depth_offset * Mpc / sw_data.metadata.boxsize[0] if xlargs.debug: percent = f"{depth_offset * Mpc / _zCen * 100:.1f}" print(( f"Imposing offset in slicing depth: {depth_offset:.2f} Mpc.\n" f"Percentage shift compared to centre: {percent} %" )) _r500 = vr_data.spherical_overdensities.r_500_rhocrit[0].to('Mpc') / vr_data.a if mask_radius[1] == 'r500': mask_radius_r500 = mask_radius[0] * _r500 else: mask_radius_r500 = unyt_quantity(mask_radius[0], units=mask_radius[1]) if inscribe_mask: mask_radius_r500 /= np.sqrt(3) region = [ _xCen - mask_radius_r500, _xCen + mask_radius_r500, _yCen - mask_radius_r500, _yCen + mask_radius_r500 ] if temperature_range is not None: temp_filter = np.where( (sw_data.gas.temperatures > temperature_range[0]) & (sw_data.gas.temperatures < temperature_range[1]) )[0] if xlargs.debug: percent = f"{len(temp_filter) / len(sw_data.gas.temperatures) * 100:.1f}" print(( f"Filtering particles by temperature: {temperature_range} K.\n" f"Total particles: {len(sw_data.gas.temperatures)}\n" f"Particles within bounds: {len(temp_filter)} = {percent} %" )) sw_data.gas.coordinates = sw_data.gas.coordinates[temp_filter] sw_data.gas.smoothing_lengths = sw_data.gas.smoothing_lengths[temp_filter] sw_data.gas.masses = sw_data.gas.masses[temp_filter] sw_data.gas.densities = sw_data.gas.densities[temp_filter] sw_data.gas.temperatures = sw_data.gas.temperatures[temp_filter] # Rotate about CoP if required center = [_xCen, _yCen, _zCen] rotate_vec = [0, 0, 1] matrix = rotation_matrix_from_vector(rotate_vec, axis='z') common_kwargs = dict( rotation_matrix=matrix, rotation_center=center, data=sw_data, resolution=self.resolution, parallel=self.parallel, region=region, slice=self.depth ) if self._project_quantity == 'entropies': number_density = (sw_data.gas.densities / mh).to('cm**-3') / mean_molecular_weight entropy = kb * sw_data.gas.temperatures / number_density ** (2 / 3) sw_data.gas.entropies_physical = entropy.to('keV*cm**2') gas_map = slice_gas(project='entropies_physical', **common_kwargs).to('keV*cm**2/Mpc**3') elif self._project_quantity == 'temperatures': sw_data.gas.mwtemps = sw_data.gas.masses * sw_data.gas.temperatures mass_weighted_temp_map = slice_gas(project='mwtemps', **common_kwargs) mass_map = slice_gas(project='masses', **common_kwargs) with np.errstate(divide='ignore', invalid='ignore'): gas_map = mass_weighted_temp_map / mass_map gas_map = gas_map.to('K') else: gas_map = slice_gas(project=self._project_quantity, **common_kwargs) units = gas_map.units gas_map = gas_map.value gas_map = np.ma.array( gas_map, mask=(gas_map <= 0.), fill_value=np.nan, copy=True, dtype=np.float64 ) output_values = [ gas_map, region, units, [_xCen, _yCen, _zCen], _r500, sw_data.metadata.z ] output_names = [ 'map', 'region', 'units', 'centre', 'r500', 'z' ] if return_type is tuple: output = tuple(output_values) elif return_type is dict: output = dict(zip(output_names, output_values)) elif return_type == 'class': OutputClass = namedtuple('OutputClass', output_names) output = OutputClass(*output_values) else: raise TypeError(f"Return type {return_type} not recognised.") return output
Configuration for imaging creator. """ import yaml from unyt import unyt_quantity from typing import List, Optional # Items to read directly from the yaml file with their defaults direct_read = { "resolution": 512, "figure_size": 8, "image_format": "jpg", "recalculate_stellar_smoothing_lengths": True, "calculate_dark_matter_smoothing_lengths": True, "centrals_only": True, "minimum_halo_mass": unyt_quantity(1e9, "Solar_Mass"), "use_binned_image_selection": False, "bin_width_in_dex": 0.5, "haloes_to_visualise_per_bin": 10, "thumbnail_image": "", } class Image(object): """ Object describing the properties of a given image, implementing defaults. """ # Name (key in the dictionary); will form part of the filename base_name: str
f"\tHalo {i:d}:\t({lines[3, i]:2.1f}, {lines[4, i]:2.1f}, {lines[5, i]:2.1f})" ) M200c = lines[1] * 1e13 R200c = lines[2] x = lines[3] y = lines[4] z = lines[5] # EAGLE-XL data path dataPath = "/cosma7/data/dp004/jch/EAGLE-XL/DMONLY/Cosma7/L0300N0564/snapshots/" snapFile = dataPath + "EAGLE-XL_L0300N0564_DMONLY_0036.hdf5" for i in range(3): print(f"Rendering halo {i}...") # Load data using mask xCen = unyt.unyt_quantity(x[i], unyt.Mpc) yCen = unyt.unyt_quantity(y[i], unyt.Mpc) zCen = unyt.unyt_quantity(z[i], unyt.Mpc) size = unyt.unyt_quantity(1 * R200c[i], unyt.Mpc) mask = sw.mask(snapFile) region = [[xCen - size, xCen + size], [yCen - size, yCen + size], [zCen - size, zCen + size]] mask.constrain_spatial(region) # Load data using mask data = sw.load(snapFile, mask=mask) posDM = data.dark_matter.coordinates / data.metadata.a coord_x = posDM[:, 0] - xCen coord_y = posDM[:, 1] - yCen coord_z = posDM[:, 2] - zCen del posDM