Quijote snapshots support (#77)

* Renaming

* Edit docs

* Delete old function

* Add a blank space

* Rename particle reader

* Add comments

* Rename

* Rename

* edit get_snapshots

* More renaming

* Remove old correction

* Add import

* Add basics of the Quijote reader

* Add a blank space

* Fix paths

* Rename function

* Fix HID and path

* Add more FoF reading

* Move definition

* Adding arguments

* Renaming

* Add kwargs for backward comp

* FoF Quijote return only hids

* Add sorting of quijote

* Add path to CSiBORG ICs snapshot

* Add support for Quijote

* initmatch paths for quijote

* Add kwargs

* Fix blank lines

* Rename kwarg

* Remove unused import

* Remove hardcoded numbers

* Update for Quijote

* Do not store velocities in QUijote ICs

* Box units mass Quijote

* Fix typo

* Ensure particles are not right at the edge

* Add structfit paths for QUuijote

* Basic CSiBORG units

* Add more quijote halo reading

* Add Quijote fitting

* Docs changes

* Docs changes
This commit is contained in:
Richard Stiskalek 2023-07-27 18:41:00 +02:00 committed by GitHub
parent e08c741fc8
commit fb4b4edf19
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 800 additions and 300 deletions

View File

@ -117,7 +117,7 @@ class BaseStructure(ABC):
assert kind in ["crit", "matter"] assert kind in ["crit", "matter"]
rho = delta_mult * self.box.box_rhoc rho = delta_mult * self.box.box_rhoc
if kind == "matter": if kind == "matter":
rho *= self.box.box_Om rho *= self.box.Om
pos = self.pos pos = self.pos
mass = self["M"] mass = self["M"]
@ -216,8 +216,8 @@ class BaseStructure(ABC):
References References
---------- ----------
[1] A Universal Angular Momentum Profile for Galactic Halos; 2001; [1] A Universal Angular Momentum Profile for Galactic Halos; 2001;
Bullock, J. S.; Dekel, A.; Kolatt, T. S.; Kravtsov, A. V.; Bullock, J. S.; Dekel, A.; Kolatt, T. S.; Kravtsov, A. V.;
Klypin, A. A.; Porciani, C.; Primack, J. R. Klypin, A. A.; Porciani, C.; Primack, J. R.
""" """
pos = self.pos pos = self.pos
mask = periodic_distance(pos, ref, boxsize=1) < rad mask = periodic_distance(pos, ref, boxsize=1) < rad

View File

@ -15,6 +15,7 @@
""" """
Support for matching halos between CSiBORG IC realisations. Support for matching halos between CSiBORG IC realisations.
""" """
from abc import ABC
from datetime import datetime from datetime import datetime
from functools import lru_cache from functools import lru_cache
from math import ceil from math import ceil
@ -26,20 +27,72 @@ from tqdm import tqdm, trange
from ..read import load_halo_particles from ..read import load_halo_particles
BCKG_HALFSIZE = 475
BOX_SIZE = 2048 class BaseMatcher(ABC):
"""
Base class for `RealisationsMatcher` and `ParticleOverlap`.
"""
_box_size = None
_bckg_halfsize = None
@property
def box_size(self):
"""
Number of cells in the box.
Returns
-------
box_size : int
"""
if self._box_size is None:
raise RuntimeError("`box_size` is not set.")
return self._box_size
@box_size.setter
def box_size(self, value):
assert isinstance(value, int)
assert value > 0
self._box_size = value
@property
def bckg_halfsize(self):
"""
Number of to each side of the centre of the box to calculate the
density field. This is because in CSiBORG we are only interested in the
high-resolution region.
Returns
-------
bckg_halfsize : int
"""
if self._bckg_halfsize is None:
raise RuntimeError("`bckg_halfsize` is not set.")
return self._bckg_halfsize
@bckg_halfsize.setter
def bckg_halfsize(self, value):
assert isinstance(value, int)
assert value > 0
self._bckg_halfsize = value
############################################################################### ###############################################################################
# Realisations matcher for calculating overlaps # # Realisations matcher for calculating overlaps #
############################################################################### ###############################################################################
class RealisationsMatcher: class RealisationsMatcher(BaseMatcher):
""" """
A tool to match haloes between IC realisations. A tool to match haloes between IC realisations.
Parameters Parameters
---------- ----------
box_size : int
Number of cells in the box.
bckg_halfsize : int
Number of to each side of the centre of the box to calculate the
density field. This is because in CSiBORG we are only interested in the
high-resolution region.
nmult : float or int, optional nmult : float or int, optional
Multiple of the sum of pair initial Lagrangian patch sizes Multiple of the sum of pair initial Lagrangian patch sizes
within which to return neighbours. By default 1. within which to return neighbours. By default 1.
@ -51,20 +104,22 @@ class RealisationsMatcher:
catalogue key. By default `totpartmass`, i.e. the total particle catalogue key. By default `totpartmass`, i.e. the total particle
mass associated with a halo. mass associated with a halo.
""" """
_nmult = None _nmult = None
_dlogmass = None _dlogmass = None
_mass_kind = None _mass_kind = None
_overlapper = None _overlapper = None
def __init__(self, nmult=1.0, dlogmass=2.0, mass_kind="totpartmass"): def __init__(self, box_size, bckg_halfsize, nmult=1.0, dlogmass=2.0,
mass_kind="totpartmass"):
assert nmult > 0 assert nmult > 0
assert dlogmass > 0 assert dlogmass > 0
assert isinstance(mass_kind, str) assert isinstance(mass_kind, str)
self.box_size = box_size
self.halfsize = bckg_halfsize
self._nmult = nmult self._nmult = nmult
self._dlogmass = dlogmass self._dlogmass = dlogmass
self._mass_kind = mass_kind self._mass_kind = mass_kind
self._overlapper = ParticleOverlap() self._overlapper = ParticleOverlap(box_size, bckg_halfsize)
@property @property
def nmult(self): def nmult(self):
@ -181,7 +236,7 @@ class RealisationsMatcher:
@lru_cache(maxsize=cache_size) @lru_cache(maxsize=cache_size)
def load_cached_halox(hid): def load_cached_halox(hid):
return load_processed_halo(hid, particlesx, halo_mapx, hid2mapx, return load_processed_halo(hid, particlesx, halo_mapx, hid2mapx,
nshift=0, ncells=BOX_SIZE) nshift=0, ncells=self.box_size)
if verbose: if verbose:
print(f"{datetime.now()}: calculating overlaps.", flush=True) print(f"{datetime.now()}: calculating overlaps.", flush=True)
@ -195,7 +250,8 @@ class RealisationsMatcher:
# Next, we find this halo's particles, total mass, minimum and # Next, we find this halo's particles, total mass, minimum and
# maximum cells and convert positions to cells. # maximum cells and convert positions to cells.
pos0, mass0, totmass0, mins0, maxs0 = load_processed_halo( pos0, mass0, totmass0, mins0, maxs0 = load_processed_halo(
k0, particles0, halo_map0, hid2map0, nshift=0, ncells=BOX_SIZE) k0, particles0, halo_map0, hid2map0, nshift=0,
ncells=self.box_size)
# We now loop over matches of this halo and calculate their # We now loop over matches of this halo and calculate their
# overlap, storing them in `_cross`. # overlap, storing them in `_cross`.
@ -267,7 +323,7 @@ class RealisationsMatcher:
@lru_cache(maxsize=cache_size) @lru_cache(maxsize=cache_size)
def load_cached_halox(hid): def load_cached_halox(hid):
return load_processed_halo(hid, particlesx, halo_mapx, hid2mapx, return load_processed_halo(hid, particlesx, halo_mapx, hid2mapx,
nshift=nshift, ncells=BOX_SIZE) nshift=nshift, ncells=self.box_size)
if verbose: if verbose:
print(f"{datetime.now()}: calculating smoothed overlaps.", print(f"{datetime.now()}: calculating smoothed overlaps.",
@ -277,7 +333,7 @@ class RealisationsMatcher:
for i, k0 in enumerate(tqdm(indxs) if verbose else indxs): for i, k0 in enumerate(tqdm(indxs) if verbose else indxs):
pos0, mass0, __, mins0, maxs0 = load_processed_halo( pos0, mass0, __, mins0, maxs0 = load_processed_halo(
k0, particles0, halo_map0, hid2map0, nshift=nshift, k0, particles0, halo_map0, hid2map0, nshift=nshift,
ncells=BOX_SIZE) ncells=self.box_size)
# Now loop over the matches and calculate the smoothed overlap. # Now loop over the matches and calculate the smoothed overlap.
_cross = numpy.full(match_indxs[i].size, numpy.nan, numpy.float32) _cross = numpy.full(match_indxs[i].size, numpy.nan, numpy.float32)
@ -327,13 +383,26 @@ def cosine_similarity(x, y):
return out return out
class ParticleOverlap: class ParticleOverlap(BaseMatcher):
r""" r"""
Class to calculate halo overlaps. The density field calculation is based on Halo overlaps calculator. The density field calculation is based on the
the nearest grid position particle assignment scheme, with optional nearest grid position particle assignment scheme, with optional Gaussian
Gaussian smoothing. smoothing.
Parameters
----------
box_size : int
Number of cells in the box.
bckg_halfsize : int
Number of to each side of the centre of the box to calculate the
density field. This is because in CSiBORG we are only interested in the
high-resolution region.
""" """
def __init__(self, box_size, bckg_halfsize):
self.box_size = box_size
self.bckg_halfsize = bckg_halfsize
def make_bckg_delta(self, particles, halo_map, hid2map, halo_cat, def make_bckg_delta(self, particles, halo_map, hid2map, halo_cat,
delta=None, verbose=False): delta=None, verbose=False):
""" """
@ -363,8 +432,8 @@ class ParticleOverlap:
------- -------
delta : 3-dimensional array delta : 3-dimensional array
""" """
cellmin = BOX_SIZE // 2 - BCKG_HALFSIZE cellmin = self.box_size // 2 - self.bckg_halfsize
cellmax = BOX_SIZE // 2 + BCKG_HALFSIZE cellmax = self.box_size // 2 + self.bckg_halfsize
ncells = cellmax - cellmin ncells = cellmax - cellmin
# We then pre-allocate the density field/check it is of the right shape # We then pre-allocate the density field/check it is of the right shape
if delta is None: if delta is None:
@ -379,7 +448,7 @@ class ParticleOverlap:
continue continue
pos, mass = pos[:, :3], pos[:, 3] pos, mass = pos[:, :3], pos[:, 3]
pos = pos2cell(pos, BOX_SIZE) pos = pos2cell(pos, self.box_size)
# We mask out particles outside the cubical high-resolution region # We mask out particles outside the cubical high-resolution region
mask = numpy.all((cellmin <= pos) & (pos < cellmax), axis=1) mask = numpy.all((cellmin <= pos) & (pos < cellmax), axis=1)
pos = pos[mask] pos = pos[mask]
@ -413,7 +482,7 @@ class ParticleOverlap:
delta : 3-dimensional array delta : 3-dimensional array
""" """
nshift = read_nshift(smooth_kwargs) nshift = read_nshift(smooth_kwargs)
cells = pos2cell(pos, BOX_SIZE) cells = pos2cell(pos, self.box_size)
# Check that minima and maxima are integers # Check that minima and maxima are integers
if not (mins is None and maxs is None): if not (mins is None and maxs is None):
assert mins.dtype.char in numpy.typecodes["AllInteger"] assert mins.dtype.char in numpy.typecodes["AllInteger"]
@ -421,12 +490,12 @@ class ParticleOverlap:
if subbox: if subbox:
if mins is None or maxs is None: if mins is None or maxs is None:
mins, maxs = get_halolims(cells, BOX_SIZE, nshift) mins, maxs = get_halolims(cells, self.box_size, nshift)
ncells = maxs - mins + 1 # To get the number of cells ncells = maxs - mins + 1 # To get the number of cells
else: else:
mins = [0, 0, 0] mins = [0, 0, 0]
ncells = (BOX_SIZE, ) * 3 ncells = (self.box_size, ) * 3
# Preallocate and fill the array # Preallocate and fill the array
delta = numpy.zeros(ncells, dtype=numpy.float32) delta = numpy.zeros(ncells, dtype=numpy.float32)
@ -472,8 +541,8 @@ class ParticleOverlap:
Calculated only if no smoothing is applied, otherwise `None`. Calculated only if no smoothing is applied, otherwise `None`.
""" """
nshift = read_nshift(smooth_kwargs) nshift = read_nshift(smooth_kwargs)
pos1 = pos2cell(pos1, BOX_SIZE) pos1 = pos2cell(pos1, self.box_size)
pos2 = pos2cell(pos2, BOX_SIZE) pos2 = pos2cell(pos2, self.box_size)
xc1, yc1, zc1 = [pos1[:, i] for i in range(3)] xc1, yc1, zc1 = [pos1[:, i] for i in range(3)]
xc2, yc2, zc2 = [pos2[:, i] for i in range(3)] xc2, yc2, zc2 = [pos2[:, i] for i in range(3)]
@ -490,7 +559,7 @@ class ParticleOverlap:
ymax = max(numpy.max(yc1), numpy.max(yc2)) + nshift ymax = max(numpy.max(yc1), numpy.max(yc2)) + nshift
zmax = max(numpy.max(zc1), numpy.max(zc2)) + nshift zmax = max(numpy.max(zc1), numpy.max(zc2)) + nshift
# Make sure shifting does not go beyond boundaries # Make sure shifting does not go beyond boundaries
xmax, ymax, zmax = [min(px, BOX_SIZE - 1) xmax, ymax, zmax = [min(px, self.box_size - 1)
for px in (xmax, ymax, zmax)] for px in (xmax, ymax, zmax)]
else: else:
xmin, ymin, zmin = [min(mins1[i], mins2[i]) for i in range(3)] xmin, ymin, zmin = [min(mins1[i], mins2[i]) for i in range(3)]
@ -573,12 +642,14 @@ class ParticleOverlap:
smooth_kwargs=smooth_kwargs) smooth_kwargs=smooth_kwargs)
if smooth_kwargs is not None: if smooth_kwargs is not None:
return calculate_overlap(delta1, delta2, cellmins, delta_bckg) return calculate_overlap(delta1, delta2, cellmins, delta_bckg,
self.box_size, self.bckg_halfsize)
# Calculate masses not given # Calculate masses not given
totmass1 = numpy.sum(mass1) if totmass1 is None else totmass1 totmass1 = numpy.sum(mass1) if totmass1 is None else totmass1
totmass2 = numpy.sum(mass2) if totmass2 is None else totmass2 totmass2 = numpy.sum(mass2) if totmass2 is None else totmass2
return calculate_overlap_indxs( return calculate_overlap_indxs(delta1, delta2, cellmins, delta_bckg,
delta1, delta2, cellmins, delta_bckg, nonzero, totmass1, totmass2) nonzero, totmass1, totmass2,
self.box_size, self.bckg_halfsize)
############################################################################### ###############################################################################
@ -725,7 +796,8 @@ def get_halolims(pos, ncells, nshift=None):
@jit(nopython=True) @jit(nopython=True)
def calculate_overlap(delta1, delta2, cellmins, delta_bckg): def calculate_overlap(delta1, delta2, cellmins, delta_bckg, box_size,
bckg_halfsize):
r""" r"""
Overlap between two halos whose density fields are evaluated on the Overlap between two halos whose density fields are evaluated on the
same grid. This is a JIT implementation, hence it is outside of the main same grid. This is a JIT implementation, hence it is outside of the main
@ -743,6 +815,12 @@ def calculate_overlap(delta1, delta2, cellmins, delta_bckg):
Summed background density field of the reference and cross simulations Summed background density field of the reference and cross simulations
calculated with particles assigned to halos at the final snapshot. calculated with particles assigned to halos at the final snapshot.
Assumed to only be sampled in cells :math:`[512, 1536)^3`. Assumed to only be sampled in cells :math:`[512, 1536)^3`.
box_size : int
Number of cells in the box.
bckg_halfsize : int
Number of to each side of the centre of the box to calculate the
density field. This is because in CSiBORG we are only interested in the
high-resolution region.
Returns Returns
------- -------
@ -751,8 +829,8 @@ def calculate_overlap(delta1, delta2, cellmins, delta_bckg):
totmass = 0.0 # Total mass of halo 1 and halo 2 totmass = 0.0 # Total mass of halo 1 and halo 2
intersect = 0.0 # Weighted intersecting mass intersect = 0.0 # Weighted intersecting mass
i0, j0, k0 = cellmins # Unpack things i0, j0, k0 = cellmins # Unpack things
bckg_size = 2 * BCKG_HALFSIZE bckg_size = 2 * bckg_halfsize
bckg_offset = BOX_SIZE // 2 - BCKG_HALFSIZE bckg_offset = box_size // 2 - bckg_halfsize
imax, jmax, kmax = delta1.shape imax, jmax, kmax = delta1.shape
for i in range(imax): for i in range(imax):
@ -777,7 +855,7 @@ def calculate_overlap(delta1, delta2, cellmins, delta_bckg):
@jit(nopython=True) @jit(nopython=True)
def calculate_overlap_indxs(delta1, delta2, cellmins, delta_bckg, nonzero, def calculate_overlap_indxs(delta1, delta2, cellmins, delta_bckg, nonzero,
mass1, mass2): mass1, mass2, box_size, bckg_halfsize):
r""" r"""
Overlap between two haloes whose density fields are evaluated on the Overlap between two haloes whose density fields are evaluated on the
same grid and `nonzero1` enumerates the non-zero cells of `delta1. This is same grid and `nonzero1` enumerates the non-zero cells of `delta1. This is
@ -801,6 +879,12 @@ def calculate_overlap_indxs(delta1, delta2, cellmins, delta_bckg, nonzero,
mass1, mass2 : floats, optional mass1, mass2 : floats, optional
Total masses of the two haloes, respectively. Optional. If not provided Total masses of the two haloes, respectively. Optional. If not provided
calculcated directly from the density field. calculcated directly from the density field.
box_size : int
Number of cells in the box.
bckg_halfsize : int
Number of to each side of the centre of the box to calculate the
density field. This is because in CSiBORG we are only interested in the
high-resolution region.
Returns Returns
------- -------
@ -808,8 +892,8 @@ def calculate_overlap_indxs(delta1, delta2, cellmins, delta_bckg, nonzero,
""" """
intersect = 0.0 # Weighted intersecting mass intersect = 0.0 # Weighted intersecting mass
i0, j0, k0 = cellmins # Unpack cell minimas i0, j0, k0 = cellmins # Unpack cell minimas
bckg_size = 2 * BCKG_HALFSIZE bckg_size = 2 * bckg_halfsize
bckg_offset = BOX_SIZE // 2 - BCKG_HALFSIZE bckg_offset = box_size // 2 - bckg_halfsize
for n in range(nonzero.shape[0]): for n in range(nonzero.shape[0]):
i, j, k = nonzero[n, :] i, j, k = nonzero[n, :]

View File

@ -23,8 +23,8 @@ from .overlap_summary import (NPairsOverlap, PairOverlap, # noqa
binned_resample_mean, get_cross_sims) # noqa binned_resample_mean, get_cross_sims) # noqa
from .paths import Paths # noqa from .paths import Paths # noqa
from .pk_summary import PKReader # noqa from .pk_summary import PKReader # noqa
from .readsim import (MmainReader, ParticleReader, halfwidth_mask, # noqa from .readsim import (MmainReader, CSiBORGReader, QuijoteReader, halfwidth_mask, # noqa
load_halo_particles, read_initcm) # noqa load_halo_particles) # noqa
from .tpcf_summary import TPCFReader # noqa from .tpcf_summary import TPCFReader # noqa
from .utils import (M200_to_R200, cartesian_to_radec, # noqa from .utils import (M200_to_R200, cartesian_to_radec, # noqa
cols_to_structured, radec_to_cartesian, read_h5, cols_to_structured, radec_to_cartesian, read_h5,

View File

@ -21,9 +21,9 @@ import numpy
from astropy import constants, units from astropy import constants, units
from astropy.cosmology import LambdaCDM from astropy.cosmology import LambdaCDM
from .readsim import ParticleReader from .readsim import CSiBORGReader, QuijoteReader
# Map of CSiBORG unit conversions
CSIBORG_CONV_NAME = { CSIBORG_CONV_NAME = {
"length": ["x", "y", "z", "peak_x", "peak_y", "peak_z", "Rs", "rmin", "length": ["x", "y", "z", "peak_x", "peak_y", "peak_z", "Rs", "rmin",
"rmax", "r200c", "r500c", "r200m", "r500m", "x0", "y0", "z0", "rmax", "r200c", "r500c", "r200m", "r500m", "x0", "y0", "z0",
@ -31,8 +31,14 @@ CSIBORG_CONV_NAME = {
"velocity": ["vx", "vy", "vz"], "velocity": ["vx", "vy", "vz"],
"mass": ["mass_cl", "totpartmass", "m200c", "m500c", "mass_mmain", "M", "mass": ["mass_cl", "totpartmass", "m200c", "m500c", "mass_mmain", "M",
"m200m", "m500m"], "m200m", "m500m"],
"density": ["rho0"]} "density": ["rho0"]
}
QUIJOTE_CONV_NAME = {
"length": ["x", "y", "z", "x0", "y0", "z0", "Rs", "r200c", "r500c",
"r200m", "r500m", "lagpatch_size"],
"mass": ["group_mass", "totpartmass", "m200c", "m500c", "m200m", "m500m"],
}
############################################################################### ###############################################################################
# Base box # # Base box #
@ -74,7 +80,7 @@ class BaseBox(ABC):
@property @property
def h(self): def h(self):
r""" r"""
The little 'h` parameter at the time of the snapshot. The little 'h' parameter at the time of the snapshot.
Returns Returns
------- -------
@ -157,12 +163,12 @@ class CSiBORGBox(BaseBox):
""" """
Read in the snapshot info file and set the units from it. Read in the snapshot info file and set the units from it.
""" """
partreader = ParticleReader(paths) partreader = CSiBORGReader(paths)
info = partreader.read_info(nsnap, nsim) info = partreader.read_info(nsnap, nsim)
pars = ["boxlen", "time", "aexp", "H0", "omega_m", "omega_l", pars = ["boxlen", "time", "aexp", "H0", "omega_m", "omega_l",
"omega_k", "omega_b", "unit_l", "unit_d", "unit_t"] "omega_k", "omega_b", "unit_l", "unit_d", "unit_t"]
for par in pars: for par in pars:
setattr(self, "_" + par, float(info[par])) setattr(self, "_" + par, info[par])
self._cosmo = LambdaCDM(H0=self._H0, Om0=self._omega_m, self._cosmo = LambdaCDM(H0=self._H0, Om0=self._omega_m,
Ode0=self._omega_l, Tcmb0=2.725 * units.K, Ode0=self._omega_l, Tcmb0=2.725 * units.K,
@ -226,7 +232,7 @@ class CSiBORGBox(BaseBox):
Returns Returns
------- -------
length : foat length : float
Length in :math:`\mathrm{ckpc}` Length in :math:`\mathrm{ckpc}`
""" """
return length * (self._unit_l / units.kpc.to(units.cm) / self._aexp) return length * (self._unit_l / units.kpc.to(units.cm) / self._aexp)
@ -243,7 +249,7 @@ class CSiBORGBox(BaseBox):
Returns Returns
------- -------
length : foat length : float
Length in box units. Length in box units.
""" """
return length / (self._unit_l / units.kpc.to(units.cm) / self._aexp) return length / (self._unit_l / units.kpc.to(units.cm) / self._aexp)
@ -260,7 +266,7 @@ class CSiBORGBox(BaseBox):
Returns Returns
------- -------
length : foat length : float
Length in box units. Length in box units.
""" """
return self.kpc2box(length * 1e3) return self.kpc2box(length * 1e3)
@ -277,7 +283,7 @@ class CSiBORGBox(BaseBox):
Returns Returns
------- -------
length : foat length : float
Length in :math:`\mathrm{ckpc}` Length in :math:`\mathrm{ckpc}`
""" """
return self.box2kpc(length) * 1e-3 return self.box2kpc(length) * 1e-3
@ -419,17 +425,110 @@ class QuijoteBox(BaseBox):
Empty keyword arguments. For backwards compatibility. Empty keyword arguments. For backwards compatibility.
""" """
def __init__(self, nsnap, **kwargs): def __init__(self, nsnap, nsim, paths):
zdict = {4: 0.0, 3: 0.5, 2: 1.0, 1: 2.0, 0: 3.0} zdict = {4: 0.0, 3: 0.5, 2: 1.0, 1: 2.0, 0: 3.0}
assert nsnap in zdict.keys(), f"`nsnap` must be in {zdict.keys()}." assert nsnap in zdict.keys(), f"`nsnap` must be in {zdict.keys()}."
self._aexp = 1 / (1 + zdict[nsnap]) self._aexp = 1 / (1 + zdict[nsnap])
self._cosmo = LambdaCDM(H0=67.11, Om0=0.3175, Ode0=0.6825, info = QuijoteReader(paths).read_info(nsnap, nsim)
Tcmb0=2.725 * units.K, Ob0=0.049) self._cosmo = LambdaCDM(H0=info["Hubble"], Om0=info["Omega_m"],
Ode0=info["Omega_l"], Tcmb0=2.725 * units.K)
self._info = info
@property @property
def boxsize(self): def boxsize(self):
return 1000. / (self._cosmo.H0.value / 100) return self._info["BoxSize"]
def box2mpc(self, length):
r"""
Convert length from box units to :math:`\mathrm{cMpc} / h`.
Parameters
----------
length : float
Length in box units.
Returns
-------
length : float
Length in :math:`\mathrm{cMpc} / h`
"""
return length * self.boxsize
def mpc2box(self, length):
r"""
Convert length from :math:`\mathrm{cMpc} / h` to box units.
Parameters
----------
length : float
Length in :math:`\mathrm{cMpc} / h`.
Returns
-------
length : float
Length in box units.
"""
return length / self.boxsize
def solarmass2box(self, mass):
r"""
Convert mass from :math:`M_\odot / h` to box units.
Parameters
----------
mass : float
Mass in :math:`M_\odot`.
Returns
-------
mass : float
Mass in box units.
"""
return mass / self._info["TotMass"]
def box2solarmass(self, mass):
r"""
Convert mass from box units to :math:`M_\odot / h`.
Parameters
----------
mass : float
Mass in box units.
Returns
-------
mass : float
Mass in :math:`M_\odot / h`.
"""
return mass * self._info["TotMass"]
def convert_from_box(self, data, names): def convert_from_box(self, data, names):
raise NotImplementedError("Conversion not implemented for Quijote.") names = [names] if isinstance(names, str) else names
transforms = {"length": self.box2mpc,
"mass": self.box2solarmass,
# "velocity": self.box2vel,
# "density": self.box2dens,
}
for name in names:
if name not in data.dtype.names:
continue
# Convert
found = False
for unittype, suppnames in QUIJOTE_CONV_NAME.items():
if name in suppnames:
data[name] = transforms[unittype](data[name])
found = True
continue
# If nothing found
if not found:
raise NotImplementedError(
f"Conversion of `{name}` is not defined.")
# # Center at the observer
# if name in ["x0", "y0", "z0"]:
# data[name] -= transforms["length"](0.5)
return data

View File

@ -22,7 +22,6 @@ from copy import deepcopy
from functools import lru_cache from functools import lru_cache
from itertools import product from itertools import product
from math import floor from math import floor
from os.path import join
import numpy import numpy
from readfof import FoF_catalog from readfof import FoF_catalog
@ -30,14 +29,14 @@ from sklearn.neighbors import NearestNeighbors
from .box_units import CSiBORGBox, QuijoteBox from .box_units import CSiBORGBox, QuijoteBox
from .paths import Paths from .paths import Paths
from .readsim import ParticleReader from .readsim import CSiBORGReader
from .utils import (add_columns, cartesian_to_radec, cols_to_structured, from .utils import (add_columns, cartesian_to_radec, cols_to_structured,
flip_cols, radec_to_cartesian, real2redshift) flip_cols, radec_to_cartesian, real2redshift)
class BaseCatalogue(ABC): class BaseCatalogue(ABC):
""" """
Base (sub)halo catalogue. Base halo catalogue.
""" """
_data = None _data = None
_paths = None _paths = None
@ -125,11 +124,8 @@ class BaseCatalogue(ABC):
def position(self, in_initial=False, cartesian=True): def position(self, in_initial=False, cartesian=True):
r""" r"""
Position components. If Cartesian, then in :math:`\mathrm{cMpc}`. If Position components. If not Cartesian, then RA is in :math:`[0, 360)`
spherical, then radius is in :math:`\mathrm{cMpc}`, RA in degrees and DEC is in :math:`[-90, 90]` degrees.
:math:`[0, 360)` degrees and DEC in :math:`[-90, 90]` degrees. Note
that the position is defined as the minimum of the gravitationl
potential.
Parameters Parameters
---------- ----------
@ -399,33 +395,33 @@ class CSiBORGHaloCatalogue(BaseCatalogue):
names and the items are a len-2 tuple of (min, max) values. In case of names and the items are a len-2 tuple of (min, max) values. In case of
no minimum or maximum, use `None`. For radial distance from the origin no minimum or maximum, use `None`. For radial distance from the origin
use `dist`. use `dist`.
with_lagpatch : bool, optional
Whether to only load halos with a resolved Lagrangian patch.
load_fitted : bool, optional load_fitted : bool, optional
Whether to load fitted quantities. Whether to load fitted quantities.
load_initial : bool, optional load_initial : bool, optional
Whether to load initial positions. Whether to load initial positions.
with_lagpatch : bool, optional
Whether to only load halos with a resolved Lagrangian patch.
rawdata : bool, optional rawdata : bool, optional
Whether to return the raw data. In this case applies no cuts and Whether to return the raw data. In this case applies no cuts and
transformations. transformations.
""" """
def __init__(self, nsim, paths, bounds={"dist": (0, 155.5 / 0.705)}, def __init__(self, nsim, paths, bounds={"dist": (0, 155.5 / 0.705)},
with_lagpatch=True, load_fitted=True, load_initial=True, load_fitted=True, load_initial=True, with_lagpatch=True,
rawdata=False): rawdata=False):
self.nsim = nsim self.nsim = nsim
self.paths = paths self.paths = paths
reader = ParticleReader(paths) reader = CSiBORGReader(paths)
self._data = reader.read_fof_halos(self.nsim) self._data = reader.read_fof_halos(self.nsim)
if load_fitted: if load_fitted:
fits = numpy.load(paths.structfit(self.nsnap, nsim)) fits = numpy.load(paths.structfit(self.nsnap, nsim, "csiborg"))
cols = [col for col in fits.dtype.names if col != "index"] cols = [col for col in fits.dtype.names if col != "index"]
X = [fits[col] for col in cols] X = [fits[col] for col in cols]
self._data = add_columns(self._data, X, cols) self._data = add_columns(self._data, X, cols)
if load_initial: if load_initial:
fits = numpy.load(paths.initmatch(nsim, "fit")) fits = numpy.load(paths.initmatch(nsim, "csiborg", "fit"))
X, cols = [], [] X, cols = [], []
for col in fits.dtype.names: for col in fits.dtype.names:
if col == "index": if col == "index":
@ -465,7 +461,7 @@ class CSiBORGHaloCatalogue(BaseCatalogue):
@property @property
def nsnap(self): def nsnap(self):
return max(self.paths.get_snapshots(self.nsim)) return max(self.paths.get_snapshots(self.nsim, "csiborg"))
@property @property
def box(self): def box(self):
@ -497,28 +493,35 @@ class QuijoteHaloCatalogue(BaseCatalogue):
nsnap : int nsnap : int
Snapshot index. Snapshot index.
origin : len-3 tuple, optional origin : len-3 tuple, optional
Where to place the origin of the box. By default the centre of the box. Where to place the origin of the box. In units of :math:`cMpc / h`.
In units of :math:`cMpc`.
bounds : dict bounds : dict
Parameter bounds to apply to the catalogue. The keys are the parameter Parameter bounds to apply to the catalogue. The keys are the parameter
names and the items are a len-2 tuple of (min, max) values. In case of names and the items are a len-2 tuple of (min, max) values. In case of
no minimum or maximum, use `None`. For radial distance from the origin no minimum or maximum, use `None`. For radial distance from the origin
use `dist`. use `dist`.
load_initial : bool, optional
Whether to load initial positions.
with_lagpatch : bool, optional
Whether to only load halos with a resolved Lagrangian patch.
rawdata : bool, optional
Whether to return the raw data. In this case applies no cuts and
transformations.
**kwargs : dict **kwargs : dict
Keyword arguments for backward compatibility. Keyword arguments for backward compatibility.
""" """
_nsnap = None _nsnap = None
_origin = None _origin = None
def __init__(self, nsim, paths, nsnap, def __init__(self, nsim, paths, nsnap, origin=[0., 0., 0.],
origin=[500 / 0.6711, 500 / 0.6711, 500 / 0.6711], bounds=None, load_initial=True, with_lagpatch=True,
bounds=None, **kwargs): rawdata=False, **kwargs):
self.paths = paths self.paths = paths
self.nsnap = nsnap self.nsnap = nsnap
self.origin = origin self.origin = origin
self._boxwidth = 1000 / 0.6711 self._box = QuijoteBox(nsnap, nsim, paths)
self._boxwidth = self.box.boxsize
fpath = join(self.paths.quijote_dir, "halos", str(nsim)) fpath = self.paths.fof_cat(nsim, "quijote")
fof = FoF_catalog(fpath, self.nsnap, long_ids=False, swap=False, fof = FoF_catalog(fpath, self.nsnap, long_ids=False, swap=False,
SFR=False, read_IDs=False) SFR=False, read_IDs=False)
@ -529,18 +532,44 @@ class QuijoteHaloCatalogue(BaseCatalogue):
("index", numpy.int32)] ("index", numpy.int32)]
data = cols_to_structured(fof.GroupLen.size, cols) data = cols_to_structured(fof.GroupLen.size, cols)
pos = fof.GroupPos / 1e3 / self.box.h pos = self.box.mpc2box(fof.GroupPos / 1e3)
vel = fof.GroupVel * (1 + self.redshift) vel = fof.GroupVel * (1 + self.redshift)
for i, p in enumerate(["x", "y", "z"]): for i, p in enumerate(["x", "y", "z"]):
data[p] = pos[:, i] - self.origin[i] data[p] = pos[:, i] - self.origin[i]
data["v" + p] = vel[:, i] data["v" + p] = vel[:, i]
data["group_mass"] = fof.GroupMass * 1e10 / self.box.h data["group_mass"] = self.box.solarmass2box(fof.GroupMass * 1e10)
data["npart"] = fof.GroupLen data["npart"] = fof.GroupLen
data["index"] = numpy.arange(data.size, dtype=numpy.int32) # We want to start indexing from 1. Index 0 is reserved for
# particles unassigned to any FoF group.
data["index"] = 1 + numpy.arange(data.size, dtype=numpy.int32)
if load_initial:
fits = numpy.load(paths.initmatch(nsim, "quijote", "fit"))
X, cols = [], []
for col in fits.dtype.names:
if col == "index":
continue
if col in ['x', 'y', 'z']:
cols.append(col + "0")
else:
cols.append(col)
X.append(fits[col])
data = add_columns(data, X, cols)
self._data = data self._data = data
if bounds is not None: if not rawdata:
self.apply_bounds(bounds) if with_lagpatch:
mask = numpy.isfinite(self._data["lagpatch_size"])
self._data = self._data[mask]
names = ["x", "y", "z", "group_mass"]
self._data = self.box.convert_from_box(self._data, names)
if load_initial:
names = ["x0", "y0", "z0", "lagpatch_size"]
self._data = self.box.convert_from_box(self._data, names)
if bounds is not None:
self.apply_bounds(bounds)
@property @property
def nsnap(self): def nsnap(self):
@ -579,7 +608,7 @@ class QuijoteHaloCatalogue(BaseCatalogue):
------- -------
box : instance of :py:class:`csiborgtools.units.BaseBox` box : instance of :py:class:`csiborgtools.units.BaseBox`
""" """
return QuijoteBox(self.nsnap) return self._box
@property @property
def origin(self): def origin(self):
@ -629,6 +658,7 @@ class QuijoteHaloCatalogue(BaseCatalogue):
cat.apply_bounds({"dist": (0, rmax)}) cat.apply_bounds({"dist": (0, rmax)})
return cat return cat
############################################################################### ###############################################################################
# Utility functions for halo catalogues # # Utility functions for halo catalogues #
############################################################################### ###############################################################################

View File

@ -186,7 +186,7 @@ class Paths:
""" """
return join(self.borg_dir, "mcmc", f"mcmc_{nsim}.h5") return join(self.borg_dir, "mcmc", f"mcmc_{nsim}.h5")
def fof_membership(self, nsim, sorted=False): def fof_membership(self, nsim, simname, sorted=False):
""" """
Path to the file containing the FoF particle membership. Path to the file containing the FoF particle membership.
@ -194,10 +194,15 @@ class Paths:
---------- ----------
nsim : int nsim : int
IC realisation index. IC realisation index.
simname : str
Simulation name. Must be one of `csiborg` or `quijote`.
sorted : bool, optional sorted : bool, optional
Whether to return path to the file that is sorted in the same Whether to return path to the file that is sorted in the same
order as the PHEW output. order as the PHEW output.
""" """
assert simname in ["csiborg", "quijote"]
if simname == "quijote":
raise RuntimeError("Quijote FoF membership is in the FoF cats..")
fdir = join(self.postdir, "FoF_membership", ) fdir = join(self.postdir, "FoF_membership", )
if not isdir(fdir): if not isdir(fdir):
mkdir(fdir) mkdir(fdir)
@ -207,20 +212,25 @@ class Paths:
fout = fout.replace(".npy", "_sorted.npy") fout = fout.replace(".npy", "_sorted.npy")
return fout return fout
def fof_cat(self, nsim): def fof_cat(self, nsim, simname):
""" r"""
Path to the FoF halo catalogue file. Path to the :math:`z = 0` FoF halo catalogue.
Parameters Parameters
---------- ----------
nsim : int nsim : int
IC realisation index. IC realisation index.
simname : str
Simulation name. Must be one of `csiborg` or `quijote`.
""" """
fdir = join(self.postdir, "FoF_membership", ) assert simname in ["csiborg", "quijote"]
if not isdir(fdir): if simname == "csiborg":
mkdir(fdir) fdir = join(self.postdir, "FoF_membership", )
warn(f"Created directory `{fdir}`.", UserWarning, stacklevel=1) if not isdir(fdir):
return join(fdir, f"halo_catalog_{nsim}_FOF.txt") mkdir(fdir)
warn(f"Created directory `{fdir}`.", UserWarning)
return join(fdir, f"halo_catalog_{nsim}_FOF.txt")
return join(self.quijote_dir, "Halos_fiducial", str(nsim))
def mmain(self, nsnap, nsim): def mmain(self, nsnap, nsim):
""" """
@ -244,7 +254,7 @@ class Paths:
return join(fdir, return join(fdir,
f"mmain_{str(nsim).zfill(5)}_{str(nsnap).zfill(5)}.npz") f"mmain_{str(nsim).zfill(5)}_{str(nsnap).zfill(5)}.npz")
def initmatch(self, nsim, kind): def initmatch(self, nsim, simname, kind):
""" """
Path to the `initmatch` files where the halo match between the Path to the `initmatch` files where the halo match between the
initial and final snapshot of a CSiBORG realisaiton is stored. initial and final snapshot of a CSiBORG realisaiton is stored.
@ -253,19 +263,30 @@ class Paths:
---------- ----------
nsim : int nsim : int
IC realisation index. IC realisation index.
simname : str
Simulation name. Must be one of `csiborg` or `quijote`.
kind : str kind : str
Type of match. Must be one of `["particles", "fit", "halomap"]`. Type of match. Must be one of `particles` or `fit`.
Returns Returns
------- -------
path : str path : str
""" """
assert kind in ["particles", "fit", "halomap"] assert simname in ["csiborg", "quijote"]
assert kind in ["particles", "fit"]
ftype = "npy" if kind == "fit" else "h5" ftype = "npy" if kind == "fit" else "h5"
fdir = join(self.postdir, "initmatch")
if simname == "csiborg":
fdir = join(self.postdir, "initmatch")
if not isdir(fdir):
mkdir(fdir)
warn(f"Created directory `{fdir}`.", UserWarning)
return join(fdir, f"{kind}_{str(nsim).zfill(5)}.{ftype}")
fdir = join(self.quijote_dir, "initmatch")
if not isdir(fdir): if not isdir(fdir):
mkdir(fdir) mkdir(fdir)
warn(f"Created directory `{fdir}`.", UserWarning, stacklevel=1) warn(f"Created directory `{fdir}`.", UserWarning)
return join(fdir, f"{kind}_{str(nsim).zfill(5)}.{ftype}") return join(fdir, f"{kind}_{str(nsim).zfill(5)}.{ftype}")
def get_ics(self, simname): def get_ics(self, simname):
@ -276,7 +297,7 @@ class Paths:
Parameters Parameters
---------- ----------
simname : str simname : str
Simulation name. Must be one of `["csiborg", "quijote"]`. Simulation name. Must be `csiborg` or `quijote`.
Returns Returns
------- -------
@ -295,75 +316,127 @@ class Paths:
except ValueError: except ValueError:
pass pass
return numpy.sort(ids) return numpy.sort(ids)
else:
# TODO here later read this from the catalogues instead.
return numpy.arange(100, dtype=int)
def ic_path(self, nsim, tonew=False): files = glob("/mnt/extraspace/rstiskalek/Quijote/Snapshots_fiducial/*")
files = [int(f.split("/")[-1]) for f in files]
return numpy.sort(files)
def snapshots(self, nsim, simname, tonew=False):
""" """
Path to a CSiBORG IC realisation folder. Path to an IC snapshots folder.
Parameters Parameters
---------- ----------
nsim : int nsim : int
IC realisation index. IC realisation index.
simname : str
Simulation name. Must be one of `csiborg` or `quijote`.
tonew : bool, optional tonew : bool, optional
Whether to return the path to the '_new' IC realisation. Whether to return the path to the '_new' IC realisation of
CSiBORG. Ignored for Quijote.
Returns Returns
------- -------
path : str path : str
""" """
fname = "ramses_out_{}" assert simname in ["csiborg", "quijote"]
if tonew: if simname == "csiborg":
fname += "_new" fname = "ramses_out_{}"
return join(self.postdir, "output", fname.format(nsim)) if tonew:
fname += "_new"
return join(self.postdir, "output", fname.format(nsim))
return join(self.srcdir, fname.format(nsim))
return join(self.srcdir, fname.format(nsim)) return join(self.quijote_dir, "Snapshots_fiducial", str(nsim))
def get_snapshots(self, nsim): def get_snapshots(self, nsim, simname):
""" """
List of available snapshots of a CSiBORG IC realisation. List of available snapshots of simulation.
Parameters Parameters
---------- ----------
nsim : int nsim : int
IC realisation index. IC realisation index.
simname : str
Simulation name. Must be one of `csiborg` or `quijote`.
Returns Returns
------- -------
snapshots : 1-dimensional array snapshots : 1-dimensional array
""" """
simpath = self.ic_path(nsim, tonew=False) simpath = self.snapshots(nsim, simname, tonew=False)
# Get all files in simpath that start with output_ if simname == "csiborg":
snaps = glob(join(simpath, "output_*")) # Get all files in simpath that start with output_
# Take just the last _00XXXX from each file and strip zeros snaps = glob(join(simpath, "output_*"))
snaps = [int(snap.split("_")[-1].lstrip("0")) for snap in snaps] # Take just the last _00XXXX from each file and strip zeros
snaps = [int(snap.split("_")[-1].lstrip("0")) for snap in snaps]
else:
snaps = glob(join(simpath, "snapdir_*"))
snaps = [int(snap.split("/")[-1].split("snapdir_")[-1])
for snap in snaps]
return numpy.sort(snaps) return numpy.sort(snaps)
def snapshot(self, nsnap, nsim): def snapshot(self, nsnap, nsim, simname):
""" """
Path to a CSiBORG IC realisation snapshot. Path to an IC realisation snapshot.
Parameters Parameters
---------- ----------
nsnap : int nsnap : int
Snapshot index. Snapshot index. For Quijote, `-1` indicates the IC snapshot.
nsim : int nsim : int
IC realisation index. IC realisation index.
simname : str
Simulation name. Must be one of `csiborg` or `quijote`.
Returns Returns
------- -------
snappath : str snappath : str
""" """
tonew = nsnap == 1 simpath = self.snapshots(nsim, simname, tonew=nsnap == 1)
simpath = self.ic_path(nsim, tonew=tonew) if simname == "csiborg":
return join(simpath, f"output_{str(nsnap).zfill(5)}") return join(simpath, f"output_{str(nsnap).zfill(5)}")
else:
if nsnap == -1:
return join(simpath, "ICs", "ics")
nsnap = str(nsnap).zfill(3)
return join(simpath, f"snapdir_{nsnap}", f"snap_{nsnap}")
def structfit(self, nsnap, nsim): def particles(self, nsim, simname):
""" """
Path to the clump or halo catalogue from `fit_halos.py`. Only CSiBORG Path to the files containing all particles of a CSiBORG realisation at
is supported. :math:`z = 0`.
Parameters
----------
nsim : int
IC realisation index.
simname : str
Simulation name. Must be one of `csiborg` or `quijote`.
Returns
-------
path : str
"""
assert simname in ["csiborg", "quijote"]
if simname == "csiborg":
fdir = join(self.postdir, "particles")
if not isdir(fdir):
makedirs(fdir)
warn(f"Created directory `{fdir}`.", UserWarning)
fname = f"parts_{str(nsim).zfill(5)}.h5"
return join(fdir, fname)
fdir = join(self.quijote_dir, "Particles_fiducial")
if not isdir(fdir):
makedirs(fdir)
warn(f"Created directory `{fdir}`.", UserWarning)
fname = f"parts_{str(nsim).zfill(5)}.h5"
return join(fdir, fname)
def structfit(self, nsnap, nsim, simname):
"""
Path to the halo catalogue from `fit_halos.py`.
Parameters Parameters
---------- ----------
@ -371,15 +444,26 @@ class Paths:
Snapshot index. Snapshot index.
nsim : int nsim : int
IC realisation index. IC realisation index.
simname : str
Simulation name. Must be one of `csiborg` or `quijote`.
Returns Returns
------- -------
path : str path : str
""" """
fdir = join(self.postdir, "structfit") assert simname in ["csiborg", "quijote"]
if simname == "csiborg":
fdir = join(self.postdir, "structfit")
if not isdir(fdir):
mkdir(fdir)
warn(f"Created directory `{fdir}`.", UserWarning, stacklevel=1)
fname = f"out_{str(nsim).zfill(5)}_{str(nsnap).zfill(5)}.npy"
return join(fdir, fname)
fdir = join(self.quijote_dir, "structfit")
if not isdir(fdir): if not isdir(fdir):
mkdir(fdir) mkdir(fdir)
warn(f"Created directory `{fdir}`.", UserWarning, stacklevel=1) warn(f"Created directory `{fdir}`.", UserWarning)
fname = f"out_{str(nsim).zfill(5)}_{str(nsnap).zfill(5)}.npy" fname = f"out_{str(nsim).zfill(5)}_{str(nsnap).zfill(5)}.npy"
return join(fdir, fname) return join(fdir, fname)
@ -409,27 +493,6 @@ class Paths:
fname = fname.replace("overlap", "overlap_smoothed") fname = fname.replace("overlap", "overlap_smoothed")
return join(fdir, fname) return join(fdir, fname)
def particles(self, nsim):
"""
Path to the files containing all particles of a CSiBORG realisation at
:math:`z = 0`.
Parameters
----------
nsim : int
IC realisation index.
Returns
-------
path : str
"""
fdir = join(self.postdir, "particles")
if not isdir(fdir):
makedirs(fdir)
warn(f"Created directory `{fdir}`.", UserWarning, stacklevel=1)
fname = f"parts_{str(nsim).zfill(5)}.h5"
return join(fdir, fname)
def field(self, kind, MAS, grid, nsim, in_rsp, smooth_scale=None): def field(self, kind, MAS, grid, nsim, in_rsp, smooth_scale=None):
r""" r"""
Path to the files containing the calculated density fields in CSiBORG. Path to the files containing the calculated density fields in CSiBORG.

View File

@ -15,34 +15,28 @@
""" """
Functions to read in the particle and clump files. Functions to read in the particle and clump files.
""" """
from abc import ABC, abstractmethod
from datetime import datetime
from gc import collect
from os.path import isfile, join from os.path import isfile, join
from warnings import warn from warnings import warn
import numpy import numpy
import readfof
import readgadget
from scipy.io import FortranFile from scipy.io import FortranFile
from tqdm import tqdm, trange from tqdm import tqdm, trange
from .paths import Paths from .paths import Paths
from .utils import cols_to_structured from .utils import cols_to_structured
###############################################################################
# Fortran particle reader #
###############################################################################
class BaseReader(ABC):
class ParticleReader:
""" """
Object to read in particle files along with their corresponding haloes. Base class for all readers.
Parameters
----------
paths : py:class`csiborgtools.read.Paths`
""" """
_paths = None _paths = None
def __init__(self, paths):
self.paths = paths
@property @property
def paths(self): def paths(self):
""" """
@ -59,9 +53,10 @@ class ParticleReader:
assert isinstance(paths, Paths) assert isinstance(paths, Paths)
self._paths = paths self._paths = paths
@abstractmethod
def read_info(self, nsnap, nsim): def read_info(self, nsnap, nsim):
""" """
Read CSiBORG simulation snapshot info. Read simulation snapshot info.
Parameters Parameters
---------- ----------
@ -73,10 +68,63 @@ class ParticleReader:
Returns Returns
------- -------
info : dict info : dict
Dictionary of information paramaters. Note that both keys and Dictionary of information paramaters.
values are strings.
""" """
snappath = self.paths.snapshot(nsnap, nsim) pass
@abstractmethod
def read_particle(self, nsnap, nsim, pars_extract, return_structured=True,
verbose=True):
"""
Read particle files of a simulation at a given snapshot and return
values of `pars_extract`.
Parameters
----------
nsnap : int
Snapshot index.
nsim : int
IC realisation index.
pars_extract : list of str
Parameters to be extracted.
return_structured : bool, optional
Whether to return a structured array or a 2-dimensional array. If
the latter, then the order of the columns is the same as the order
of `pars_extract`. However, enforces single-precision floating
point format for all columns.
verbose : bool, optional
Verbosity flag while for reading in the files.
Returns
-------
out : structured array or 2-dimensional array
Particle information.
pids : 1-dimensional array
Particle IDs.
"""
pass
###############################################################################
# CSiBORG particle reader #
###############################################################################
class CSiBORGReader:
"""
Object to read in CSiBORG snapshots from the binary files and halo
catalogues.
Parameters
----------
paths : py:class`csiborgtools.read.Paths`
"""
def __init__(self, paths):
self.paths = paths
def read_info(self, nsnap, nsim):
snappath = self.paths.snapshot(nsnap, nsim, "csiborg")
filename = join(snappath, "info_{}.txt".format(str(nsnap).zfill(5))) filename = join(snappath, "info_{}.txt".format(str(nsnap).zfill(5)))
with open(filename, "r") as f: with open(filename, "r") as f:
info = f.read().split() info = f.read().split()
@ -87,7 +135,7 @@ class ParticleReader:
keys = info[eqs - 1] keys = info[eqs - 1]
vals = info[eqs + 1] vals = info[eqs + 1]
return {key: val for key, val in zip(keys, vals)} return {key: convert_str_to_num(val) for key, val in zip(keys, vals)}
def open_particle(self, nsnap, nsim, verbose=True): def open_particle(self, nsnap, nsim, verbose=True):
""" """
@ -109,7 +157,7 @@ class ParticleReader:
partfiles : list of `scipy.io.FortranFile` partfiles : list of `scipy.io.FortranFile`
Opened part files. Opened part files.
""" """
snappath = self.paths.snapshot(nsnap, nsim) snappath = self.paths.snapshot(nsnap, nsim, "csiborg")
ncpu = int(self.read_info(nsnap, nsim)["ncpu"]) ncpu = int(self.read_info(nsnap, nsim)["ncpu"])
nsnap = str(nsnap).zfill(5) nsnap = str(nsnap).zfill(5)
if verbose: if verbose:
@ -192,33 +240,6 @@ class ParticleReader:
def read_particle(self, nsnap, nsim, pars_extract, return_structured=True, def read_particle(self, nsnap, nsim, pars_extract, return_structured=True,
verbose=True): verbose=True):
"""
Read particle files of a simulation at a given snapshot and return
values of `pars_extract`.
Parameters
----------
nsnap : int
Snapshot index.
nsim : int
IC realisation index.
pars_extract : list of str
Parameters to be extracted.
return_structured : bool, optional
Whether to return a structured array or a 2-dimensional array. If
the latter, then the order of the columns is the same as the order
of `pars_extract`. However, enforces single-precision floating
point format for all columns.
verbose : bool, optional
Verbosity flag while for reading the CPU outputs.
Returns
-------
out : structured array or 2-dimensional array
Particle information.
pids : 1-dimensional array
Particle IDs.
"""
# Open the particle files # Open the particle files
nparts, partfiles = self.open_particle(nsnap, nsim, verbose=verbose) nparts, partfiles = self.open_particle(nsnap, nsim, verbose=verbose)
if verbose: if verbose:
@ -299,11 +320,11 @@ class ParticleReader:
""" """
nsnap = str(nsnap).zfill(5) nsnap = str(nsnap).zfill(5)
cpu = str(cpu + 1).zfill(5) cpu = str(cpu + 1).zfill(5)
fpath = join(self.paths.ic_path(nsim, tonew=False), f"output_{nsnap}", fpath = join(self.paths.snapshots(nsim, "csiborg", tonew=False),
f"unbinding_{nsnap}.out{cpu}") f"output_{nsnap}", f"unbinding_{nsnap}.out{cpu}")
return FortranFile(fpath) return FortranFile(fpath)
def read_clumpid(self, nsnap, nsim, verbose=True): def read_phew_clumpid(self, nsnap, nsim, verbose=True):
""" """
Read PHEW clump IDs of particles from unbinding files. This halo finder Read PHEW clump IDs of particles from unbinding files. This halo finder
was used when running the catalogue. was used when running the catalogue.
@ -337,7 +358,7 @@ class ParticleReader:
return clumpid return clumpid
def read_clumps(self, nsnap, nsim, cols=None): def read_phew_clups(self, nsnap, nsim, cols=None):
""" """
Read in a PHEW clump file `clump_xxXXX.dat`. Read in a PHEW clump file `clump_xxXXX.dat`.
@ -356,7 +377,7 @@ class ParticleReader:
out : structured array out : structured array
""" """
nsnap = str(nsnap).zfill(5) nsnap = str(nsnap).zfill(5)
fname = join(self.paths.ic_path(nsim, tonew=False), fname = join(self.paths.snapshots(nsim, "csiborg", tonew=False),
"output_{}".format(nsnap), "output_{}".format(nsnap),
"clump_{}.dat".format(nsnap)) "clump_{}.dat".format(nsnap))
if not isfile(fname): if not isfile(fname):
@ -387,7 +408,7 @@ class ParticleReader:
out[col] = data[:, clump_cols[col][0]] out[col] = data[:, clump_cols[col][0]]
return out return out
def read_fof_hids(self, nsim): def read_fof_hids(self, nsim, **kwargs):
""" """
Read in the FoF particle halo membership IDs that are sorted to match Read in the FoF particle halo membership IDs that are sorted to match
the PHEW output. the PHEW output.
@ -396,13 +417,16 @@ class ParticleReader:
---------- ----------
nsim : int nsim : int
IC realisation index. IC realisation index.
**kwargs : dict
Keyword arguments for backward compatibility.
Returns Returns
------- -------
hids : 1-dimensional array hids : 1-dimensional array
Halo IDs of particles. Halo IDs of particles.
""" """
return numpy.load(self.paths.fof_membership(nsim, sorted=True)) return numpy.load(self.paths.fof_membership(nsim, "csiborg",
sorted=True))
def read_fof_halos(self, nsim): def read_fof_halos(self, nsim):
""" """
@ -417,7 +441,7 @@ class ParticleReader:
------- -------
cat : structured array cat : structured array
""" """
fpath = self.paths.fof_cat(nsim) fpath = self.paths.fof_cat(nsim, "csiborg")
hid = numpy.genfromtxt(fpath, usecols=0, dtype=numpy.int32) hid = numpy.genfromtxt(fpath, usecols=0, dtype=numpy.int32)
pos = numpy.genfromtxt(fpath, usecols=(1, 2, 3), dtype=numpy.float32) pos = numpy.genfromtxt(fpath, usecols=(1, 2, 3), dtype=numpy.float32)
totmass = numpy.genfromtxt(fpath, usecols=4, dtype=numpy.float32) totmass = numpy.genfromtxt(fpath, usecols=4, dtype=numpy.float32)
@ -437,7 +461,7 @@ class ParticleReader:
############################################################################### ###############################################################################
# Summed substructure catalogue # # Summed substructure PHEW catalogue for CSiBORG #
############################################################################### ###############################################################################
@ -467,8 +491,8 @@ class MmainReader:
Parameters Parameters
---------- ----------
clumparr : structured array clumparr : structured array
Clump array. Read from `ParticleReader.read_clumps`. Must contain Clump array. Read from `CSiBORGReader.read_phew_clups`. Must
`index` and `parent` columns. contain `index` and `parent` columns.
verbose : bool, optional verbose : bool, optional
Verbosity flag. Verbosity flag.
@ -522,10 +546,10 @@ class MmainReader:
The ultimate parent halo index for every clump, i.e. referring to The ultimate parent halo index for every clump, i.e. referring to
its ultimate parent clump. its ultimate parent clump.
""" """
nsnap = max(self.paths.get_snapshots(nsim)) nsnap = max(self.paths.get_snapshots(nsim, "csiborg"))
partreader = ParticleReader(self.paths) partreader = CSiBORGReader(self.paths)
cols = ["index", "parent", "mass_cl", 'x', 'y', 'z'] cols = ["index", "parent", "mass_cl", 'x', 'y', 'z']
clumparr = partreader.read_clumps(nsnap, nsim, cols) clumparr = partreader.read_phew_clups(nsnap, nsim, cols)
ultimate_parent = self.find_parents(clumparr, verbose=verbose) ultimate_parent = self.find_parents(clumparr, verbose=verbose)
mask_main = clumparr["index"] == clumparr["parent"] mask_main = clumparr["index"] == clumparr["parent"]
@ -548,37 +572,162 @@ class MmainReader:
out["subfrac"] = 1 - clumparr["mass_cl"][mask_main] / out["M"] out["subfrac"] = 1 - clumparr["mass_cl"][mask_main] / out["M"]
return out, ultimate_parent return out, ultimate_parent
############################################################################### ###############################################################################
# Supplementary reading functions # # Quijote particle reader #
############################################################################### ###############################################################################
def read_initcm(nsim, srcdir, fname="clump_{}_cm.npy"): class QuijoteReader:
""" """
Read `clump_cm`, i.e. the center of mass of a clump at redshift z = 70. Object to read in Quijote snapshots from the binary files.
If the file does not exist returns `None`.
Parameters Parameters
---------- ----------
nsim : int paths : py:class`csiborgtools.read.Paths`
IC realisation index.
srcdir : str
Path to the folder containing the files.
fname : str, optional
File name convention. By default `clump_cm_{}.npy`, where the
substituted value is `nsim`.
Returns
-------
out : structured array
""" """
fpath = join(srcdir, fname.format(nsim))
try: def __init__(self, paths):
return numpy.load(fpath) self.paths = paths
except FileNotFoundError:
warn("File {} does not exist.".format(fpath), UserWarning, def read_info(self, nsnap, nsim):
stacklevel=1) snapshot = self.paths.snapshot(nsnap, nsim, "quijote")
return None header = readgadget.header(snapshot)
out = {"BoxSize": header.boxsize / 1e3, # Mpc/h
"Nall": header.nall[1], # Tot num of particles
"PartMass": header.massarr[1] * 1e10, # Part mass in Msun/h
"Omega_m": header.omega_m,
"Omega_l": header.omega_l,
"h": header.hubble,
"redshift": header.redshift,
}
out["TotMass"] = out["Nall"] * out["PartMass"]
out["Hubble"] = (100.0 * numpy.sqrt(
header.omega_m * (1.0 + header.redshift)**3 + header.omega_l))
return out
def read_particle(self, nsnap, nsim, pars_extract=None,
return_structured=True, verbose=True):
assert pars_extract in [None, "pids"]
snapshot = self.paths.snapshot(nsnap, nsim, "quijote")
info = self.read_info(nsnap, nsim)
ptype = [1] # DM in Gadget speech
if verbose:
print(f"{datetime.now()}: reading particle IDs.")
pids = readgadget.read_block(snapshot, "ID ", ptype)
if pars_extract == "pids":
return None, pids
if return_structured:
dtype = {"names": ['x', 'y', 'z', 'vx', 'vy', 'vz', 'M'],
"formats": [numpy.float32] * 7}
out = numpy.full(info["Nall"], numpy.nan, dtype=dtype)
else:
out = numpy.full((info["Nall"], 7), numpy.nan, dtype=numpy.float32)
if verbose:
print(f"{datetime.now()}: reading particle positions.")
pos = readgadget.read_block(snapshot, "POS ", ptype) / 1e3 # Mpc/h
pos /= info["BoxSize"] # Box units
for i, p in enumerate(['x', 'y', 'z']):
if return_structured:
out[p] = pos[:, i]
else:
out[:, i] = pos[:, i]
del pos
collect()
if verbose:
print(f"{datetime.now()}: reading particle velocities.")
# NOTE convert to box units.
vel = readgadget.read_block(snapshot, "VEL ", ptype) # km/s
vel *= (1 + info["redshift"])
for i, v in enumerate(['vx', 'vy', 'vz']):
if return_structured:
out[v] = vel[:, i]
else:
out[:, i + 3] = vel[:, i]
del vel
collect()
if verbose:
print(f"{datetime.now()}: reading particle masses.")
if return_structured:
out["M"] = info["PartMass"] / info["TotMass"]
else:
out[:, 6] = info["PartMass"] / info["TotMass"]
return out, pids
def read_fof_hids(self, nsnap, nsim, verbose=True, **kwargs):
"""
Read the FoF group membership of particles. Unassigned particles have
FoF group ID 0.
Parameters
----------
nsnap : int
Snapshot index.
nsim : int
IC realisation index.
verbose : bool, optional
Verbosity flag.
**kwargs : dict
Keyword arguments for backward compatibility.
Returns
-------
out : 1-dimensional array of shape `(nparticles, )`
Group membership of particles.
"""
redshift = {4: 0.0, 3: 0.5, 2: 1.0, 1: 2.0, 0: 3.0}.get(nsnap, None)
if redshift is None:
raise ValueError(f"Redshift of snapshot {nsnap} is not known.")
path = self.paths.fof_cat(nsim, "quijote")
cat = readfof.FoF_catalog(path, nsnap)
# Read the particle IDs of the snapshot
__, pids = self.read_particle(nsnap, nsim, pars_extract="pids",
verbose=verbose)
# Read the FoF particle membership. These are only assigned particles.
if verbose:
print(f"{datetime.now()}: reading the FoF particle membership.",
flush=True)
group_pids = cat.GroupIDs
group_len = cat.GroupLen
# Create a mapping from particle ID to FoF group ID.
if verbose:
print(f"{datetime.now()}: creating the particle to FoF ID to map.",
flush=True)
ks = numpy.insert(numpy.cumsum(group_len), 0, 0)
pid2hid = numpy.full((group_pids.size, 2), numpy.nan,
dtype=numpy.uint32)
for i, (k0, kf) in enumerate(zip(ks[:-1], ks[1:])):
pid2hid[k0:kf, 0] = i + 1
pid2hid[k0:kf, 1] = group_pids[k0:kf]
pid2hid = {pid: hid for hid, pid in pid2hid}
# Create the final array of hids matchign the snapshot array.
# Unassigned particles have hid 0.
if verbose:
print(f"{datetime.now()}: creating the final hid array.",
flush=True)
hids = numpy.full(pids.size, 0, dtype=numpy.uint32)
for i in trange(pids.size) if verbose else range(pids.size):
hids[i] = pid2hid.get(pids[i], 0)
return hids
###############################################################################
# Supplementary reading functions #
###############################################################################
def halfwidth_mask(pos, hw): def halfwidth_mask(pos, hw):
@ -627,3 +776,27 @@ def load_halo_particles(hid, particles, halo_map, hid2map):
return particles[k0:kf + 1, :] return particles[k0:kf + 1, :]
except KeyError: except KeyError:
return None return None
def convert_str_to_num(s):
"""
Convert a string representation of a number to its appropriate numeric type
(int or float).
Parameters
----------
s : str
The string representation of the number.
Returns
-------
num : int or float
"""
try:
return int(s)
except ValueError:
try:
return float(s)
except ValueError:
warn(f"Cannot convert string '{s}' to number", UserWarning)
return s

View File

@ -51,7 +51,7 @@ MAS = "CIC" # mass asignment scheme
paths = csiborgtools.read.Paths(**csiborgtools.paths_glamdring) paths = csiborgtools.read.Paths(**csiborgtools.paths_glamdring)
box = csiborgtools.read.CSiBORGBox(paths) box = csiborgtools.read.CSiBORGBox(paths)
reader = csiborgtools.read.ParticleReader(paths) reader = csiborgtools.read.CSiBORGReader(paths)
ics = paths.get_ics("csiborg") ics = paths.get_ics("csiborg")
nsims = len(ics) nsims = len(ics)
@ -66,8 +66,8 @@ jobs = csiborgtools.utils.split_jobs(nsims, nproc)[rank]
for n in jobs: for n in jobs:
print(f"Rank {rank} at {datetime.now()}: saving {n}th delta.", flush=True) print(f"Rank {rank} at {datetime.now()}: saving {n}th delta.", flush=True)
nsim = ics[n] nsim = ics[n]
particles = reader.read_particle(max(paths.get_snapshots(nsim)), nsim, particles = reader.read_particle(max(paths.get_snapshots(nsim, "csiborg")),
["x", "y", "z", "M"], verbose=False) nsim, ["x", "y", "z", "M"], verbose=False)
# Halfwidth -- particle selection # Halfwidth -- particle selection
if args.halfwidth < 0.5: if args.halfwidth < 0.5:
particles = csiborgtools.read.halfwidth_select( particles = csiborgtools.read.halfwidth_select(

View File

@ -58,9 +58,10 @@ def density_field(nsim, parser_args, to_save=True):
field : 3-dimensional array field : 3-dimensional array
""" """
paths = csiborgtools.read.Paths(**csiborgtools.paths_glamdring) paths = csiborgtools.read.Paths(**csiborgtools.paths_glamdring)
nsnap = max(paths.get_snapshots(nsim)) nsnap = max(paths.get_snapshots(nsim, "csiborg"))
box = csiborgtools.read.CSiBORGBox(nsnap, nsim, paths) box = csiborgtools.read.CSiBORGBox(nsnap, nsim, paths)
parts = csiborgtools.read.read_h5(paths.particles(nsim))["particles"] parts = csiborgtools.read.read_h5(paths.particles(nsim, "csiborg"))
parts = parts["particles"]
gen = csiborgtools.field.DensityField(box, parser_args.MAS) gen = csiborgtools.field.DensityField(box, parser_args.MAS)
if parser_args.kind == "density": if parser_args.kind == "density":
@ -114,9 +115,10 @@ def velocity_field(nsim, parser_args, to_save=True):
"Smoothed velocity field is not implemented.") "Smoothed velocity field is not implemented.")
paths = csiborgtools.read.Paths(**csiborgtools.paths_glamdring) paths = csiborgtools.read.Paths(**csiborgtools.paths_glamdring)
mpart = 1.1641532e-10 # Particle mass in CSiBORG simulations. mpart = 1.1641532e-10 # Particle mass in CSiBORG simulations.
nsnap = max(paths.get_snapshots(nsim)) nsnap = max(paths.get_snapshots(nsim, "csiborg"))
box = csiborgtools.read.CSiBORGBox(nsnap, nsim, paths) box = csiborgtools.read.CSiBORGBox(nsnap, nsim, paths)
parts = csiborgtools.read.read_h5(paths.particles(nsim))["particles"] parts = csiborgtools.read.read_h5(paths.particles(nsim, "csiborg"))
parts = parts["particles"]
gen = csiborgtools.field.VelocityField(box, parser_args.MAS) gen = csiborgtools.field.VelocityField(box, parser_args.MAS)
field = gen(parts, parser_args.grid, mpart, verbose=parser_args.verbose) field = gen(parts, parser_args.grid, mpart, verbose=parser_args.verbose)
@ -152,7 +154,7 @@ def potential_field(nsim, parser_args, to_save=True):
potential : 3-dimensional array potential : 3-dimensional array
""" """
paths = csiborgtools.read.Paths(**csiborgtools.paths_glamdring) paths = csiborgtools.read.Paths(**csiborgtools.paths_glamdring)
nsnap = max(paths.get_snapshots(nsim)) nsnap = max(paths.get_snapshots(nsim, "csiborg"))
box = csiborgtools.read.CSiBORGBox(nsnap, nsim, paths) box = csiborgtools.read.CSiBORGBox(nsnap, nsim, paths)
# Load the real space overdensity field # Load the real space overdensity field
@ -168,7 +170,8 @@ def potential_field(nsim, parser_args, to_save=True):
field = gen(rho) field = gen(rho)
if parser_args.in_rsp: if parser_args.in_rsp:
parts = csiborgtools.read.read_h5(paths.particles(nsim))["particles"] parts = csiborgtools.read.read_h5(paths.particles(nsim, "csiborg"))
parts = parts["particles"]
field = csiborgtools.field.field2rsp(*field, parts=parts, box=box, field = csiborgtools.field.field2rsp(*field, parts=parts, box=box,
verbose=parser_args.verbose) verbose=parser_args.verbose)
if to_save: if to_save:
@ -207,7 +210,7 @@ def radvel_field(nsim, parser_args, to_save=True):
raise NotImplementedError( raise NotImplementedError(
"Smoothed radial vel. field not implemented.") "Smoothed radial vel. field not implemented.")
paths = csiborgtools.read.Paths(**csiborgtools.paths_glamdring) paths = csiborgtools.read.Paths(**csiborgtools.paths_glamdring)
nsnap = max(paths.get_snapshots(nsim)) nsnap = max(paths.get_snapshots(nsim, "csiborg"))
box = csiborgtools.read.CSiBORGBox(nsnap, nsim, paths) box = csiborgtools.read.CSiBORGBox(nsnap, nsim, paths)
vel = numpy.load(paths.field("velocity", parser_args.MAS, parser_args.grid, vel = numpy.load(paths.field("velocity", parser_args.MAS, parser_args.grid,
@ -245,7 +248,7 @@ def environment_field(nsim, parser_args, to_save=True):
env : 3-dimensional array env : 3-dimensional array
""" """
paths = csiborgtools.read.Paths(**csiborgtools.paths_glamdring) paths = csiborgtools.read.Paths(**csiborgtools.paths_glamdring)
nsnap = max(paths.get_snapshots(nsim)) nsnap = max(paths.get_snapshots(nsim, "csiborg"))
box = csiborgtools.read.CSiBORGBox(nsnap, nsim, paths) box = csiborgtools.read.CSiBORGBox(nsnap, nsim, paths)
density_gen = csiborgtools.field.DensityField(box, parser_args.MAS) density_gen = csiborgtools.field.DensityField(box, parser_args.MAS)
gen = csiborgtools.field.TidalTensorField(box, parser_args.MAS) gen = csiborgtools.field.TidalTensorField(box, parser_args.MAS)
@ -268,7 +271,8 @@ def environment_field(nsim, parser_args, to_save=True):
# Optionally drag the field to RSP. # Optionally drag the field to RSP.
if parser_args.in_rsp: if parser_args.in_rsp:
parts = csiborgtools.read.read_h5(paths.particles(nsim))["particles"] parts = csiborgtools.read.read_h5(paths.particles(nsim, "csiborg"))
parts = parts["particles"]
fields = (tensor_field.T00, tensor_field.T11, tensor_field.T22, fields = (tensor_field.T00, tensor_field.T11, tensor_field.T22,
tensor_field.T01, tensor_field.T02, tensor_field.T12) tensor_field.T01, tensor_field.T02, tensor_field.T12)

View File

@ -81,8 +81,8 @@ def _main(nsim, simname, verbose):
verbose : bool verbose : bool
Verbosity flag. Verbosity flag.
""" """
if simname == "quijote": # if simname == "quijote":
raise NotImplementedError("Quijote not implemented yet.") # raise NotImplementedError("Quijote not implemented yet.")
cols = [("index", numpy.int32), cols = [("index", numpy.int32),
("npart", numpy.int32), ("npart", numpy.int32),
@ -95,17 +95,22 @@ def _main(nsim, simname, verbose):
("m200c", numpy.float32), ("m200c", numpy.float32),
("lambda200c", numpy.float32),] ("lambda200c", numpy.float32),]
nsnap = max(paths.get_snapshots(nsim)) nsnap = max(paths.get_snapshots(nsim, simname))
box = csiborgtools.read.CSiBORGBox(nsnap, nsim, paths) if simname == "csiborg":
box = csiborgtools.read.CSiBORGBox(nsnap, nsim, paths)
cat = csiborgtools.read.CSiBORGHaloCatalogue(
nsim, paths, with_lagpatch=False, load_initial=False, rawdata=True,
load_fitted=False)
else:
box = csiborgtools.read.QuijoteBox(nsnap, nsim, paths)
cat = csiborgtools.read.QuijoteHaloCatalogue(
nsim, paths, nsnap, load_initial=False, rawdata=True)
# Particle archive # Particle archive
f = csiborgtools.read.read_h5(paths.particles(nsim)) f = csiborgtools.read.read_h5(paths.particles(nsim, simname))
particles = f["particles"] particles = f["particles"]
halo_map = f["halomap"] halo_map = f["halomap"]
hid2map = {hid: i for i, hid in enumerate(halo_map[:, 0])} hid2map = {hid: i for i, hid in enumerate(halo_map[:, 0])}
cat = csiborgtools.read.CSiBORGHaloCatalogue(
nsim, paths, with_lagpatch=False, load_initial=False, rawdata=True,
load_fitted=False)
out = csiborgtools.read.cols_to_structured(len(cat), cols) out = csiborgtools.read.cols_to_structured(len(cat), cols)
for i in trange(len(cat)) if verbose else range(len(cat)): for i in trange(len(cat)) if verbose else range(len(cat)):
@ -121,7 +126,7 @@ def _main(nsim, simname, verbose):
for key in _out.keys(): for key in _out.keys():
out[key][i] = _out[key] out[key][i] = _out[key]
fout = paths.structfit(nsnap, nsim) fout = paths.structfit(nsnap, nsim, simname)
if verbose: if verbose:
print(f"Saving to `{fout}`.", flush=True) print(f"Saving to `{fout}`.", flush=True)
numpy.save(fout, out) numpy.save(fout, out)

View File

@ -50,9 +50,6 @@ def _main(nsim, simname, verbose):
verbose : bool verbose : bool
Verbosity flag. Verbosity flag.
""" """
if simname == "quijote":
raise NotImplementedError("Quijote not implemented yet.")
paths = csiborgtools.read.Paths(**csiborgtools.paths_glamdring) paths = csiborgtools.read.Paths(**csiborgtools.paths_glamdring)
cols = [("index", numpy.int32), cols = [("index", numpy.int32),
("x", numpy.float32), ("x", numpy.float32),
@ -61,15 +58,26 @@ def _main(nsim, simname, verbose):
("lagpatch_size", numpy.float32), ("lagpatch_size", numpy.float32),
("lagpatch_ncells", numpy.int32),] ("lagpatch_ncells", numpy.int32),]
parts = csiborgtools.read.read_h5(paths.initmatch(nsim, "particles")) fname = paths.initmatch(nsim, simname, "particles")
parts = csiborgtools.read.read_h5(fname)
parts = parts['particles'] parts = parts['particles']
halo_map = csiborgtools.read.read_h5(paths.particles(nsim)) halo_map = csiborgtools.read.read_h5(paths.particles(nsim, simname))
halo_map = halo_map["halomap"] halo_map = halo_map["halomap"]
cat = csiborgtools.read.CSiBORGHaloCatalogue( if simname == "csiborg":
nsim, paths, rawdata=True, load_fitted=False, load_initial=False) cat = csiborgtools.read.CSiBORGHaloCatalogue(
nsim, paths, rawdata=True, load_fitted=False, load_initial=False)
else:
cat = csiborgtools.read.QuijoteHaloCatalogue(nsim, paths, nsnap=4)
hid2map = {hid: i for i, hid in enumerate(halo_map[:, 0])} hid2map = {hid: i for i, hid in enumerate(halo_map[:, 0])}
# Initialise the overlapper.
if simname == "csiborg":
kwargs = {"box_size": 2048, "bckg_halfsize": 475}
else:
kwargs = {"box_size": 512, "bckg_halfsize": 256}
overlapper = csiborgtools.match.ParticleOverlap(**kwargs)
out = csiborgtools.read.cols_to_structured(len(cat), cols) out = csiborgtools.read.cols_to_structured(len(cat), cols)
for i, hid in enumerate(tqdm(cat["index"]) if verbose else cat["index"]): for i, hid in enumerate(tqdm(cat["index"]) if verbose else cat["index"]):
out["index"][i] = hid out["index"][i] = hid
@ -88,12 +96,11 @@ def _main(nsim, simname, verbose):
out["lagpatch_size"][i] = numpy.percentile(distances, 99) out["lagpatch_size"][i] = numpy.percentile(distances, 99)
# Calculate the number of cells with > 0 density. # Calculate the number of cells with > 0 density.
overlapper = csiborgtools.match.ParticleOverlap()
delta = overlapper.make_delta(pos, mass, subbox=True) delta = overlapper.make_delta(pos, mass, subbox=True)
out["lagpatch_ncells"][i] = csiborgtools.fits.delta2ncells(delta) out["lagpatch_ncells"][i] = csiborgtools.fits.delta2ncells(delta)
# Now save it # Now save it
fout = paths.initmatch(nsim, "fit") fout = paths.initmatch(nsim, simname, "fit")
if verbose: if verbose:
print(f"{datetime.now()}: dumping fits to .. `{fout}`.", flush=True) print(f"{datetime.now()}: dumping fits to .. `{fout}`.", flush=True)
with open(fout, "wb") as f: with open(fout, "wb") as f:

View File

@ -30,12 +30,15 @@ except ModuleNotFoundError:
def pair_match(nsim0, nsimx, sigma, smoothen, verbose): def pair_match(nsim0, nsimx, sigma, smoothen, verbose):
# TODO fix this.
simname = "csiborg"
overlapper_kwargs = {"box_size": 512, "bckg_halfsize": 475}
from csiborgtools.read import CSiBORGHaloCatalogue, read_h5 from csiborgtools.read import CSiBORGHaloCatalogue, read_h5
paths = csiborgtools.read.Paths(**csiborgtools.paths_glamdring) paths = csiborgtools.read.Paths(**csiborgtools.paths_glamdring)
smooth_kwargs = {"sigma": sigma, "mode": "constant", "cval": 0.0} smooth_kwargs = {"sigma": sigma, "mode": "constant", "cval": 0.0}
overlapper = csiborgtools.match.ParticleOverlap() overlapper = csiborgtools.match.ParticleOverlap(**overlapper_kwargs)
matcher = csiborgtools.match.RealisationsMatcher() matcher = csiborgtools.match.RealisationsMatcher(**overlapper_kwargs)
# Load the raw catalogues (i.e. no selection) including the initial CM # Load the raw catalogues (i.e. no selection) including the initial CM
# positions and the particle archives. # positions and the particle archives.
@ -45,12 +48,12 @@ def pair_match(nsim0, nsimx, sigma, smoothen, verbose):
catx = CSiBORGHaloCatalogue(nsimx, paths, load_initial=True, bounds=bounds, catx = CSiBORGHaloCatalogue(nsimx, paths, load_initial=True, bounds=bounds,
with_lagpatch=True, load_clumps_cat=True) with_lagpatch=True, load_clumps_cat=True)
clumpmap0 = read_h5(paths.particles(nsim0))["clumpmap"] clumpmap0 = read_h5(paths.particles(nsim0, simname))["clumpmap"]
parts0 = read_h5(paths.initmatch(nsim0, "particles"))["particles"] parts0 = read_h5(paths.initmatch(nsim0, simname, "particles"))["particles"]
clid2map0 = {clid: i for i, clid in enumerate(clumpmap0[:, 0])} clid2map0 = {clid: i for i, clid in enumerate(clumpmap0[:, 0])}
clumpmapx = read_h5(paths.particles(nsimx))["clumpmap"] clumpmapx = read_h5(paths.particles(nsimx, simname))["clumpmap"]
partsx = read_h5(paths.initmatch(nsimx, "particles"))["particles"] partsx = read_h5(paths.initmatch(nsimx, simname, "particles"))["particles"]
clid2mapx = {clid: i for i, clid in enumerate(clumpmapx[:, 0])} clid2mapx = {clid: i for i, clid in enumerate(clumpmapx[:, 0])}
# We generate the background density fields. Loads halos's particles one by # We generate the background density fields. Loads halos's particles one by

View File

@ -57,7 +57,7 @@ def copy_membership(nsim, verbose=True):
print(f"Loading from ... `{fpath}`.") print(f"Loading from ... `{fpath}`.")
data = numpy.genfromtxt(fpath, dtype=int) data = numpy.genfromtxt(fpath, dtype=int)
fout = paths.fof_membership(nsim) fout = paths.fof_membership(nsim, "csiborg")
if verbose: if verbose:
print(f"Saving to ... `{fout}`.") print(f"Saving to ... `{fout}`.")
numpy.save(fout, data) numpy.save(fout, data)
@ -77,7 +77,7 @@ def copy_catalogue(nsim, verbose=True):
paths = csiborgtools.read.Paths(**csiborgtools.paths_glamdring) paths = csiborgtools.read.Paths(**csiborgtools.paths_glamdring)
source = join("/mnt/extraspace/jeg/greenwhale/Constrained_Sims", source = join("/mnt/extraspace/jeg/greenwhale/Constrained_Sims",
f"sim_{nsim}/halo_catalog_{nsim}_FOF.txt") f"sim_{nsim}/halo_catalog_{nsim}_FOF.txt")
dest = paths.fof_cat(nsim) dest = paths.fof_cat(nsim, "csiborg")
if verbose: if verbose:
print("Copying`{}` to `{}`.".format(source, dest)) print("Copying`{}` to `{}`.".format(source, dest))
copy(source, dest) copy(source, dest)
@ -96,14 +96,14 @@ def sort_fofid(nsim, verbose=True):
Verbosity flag. Verbosity flag.
""" """
paths = csiborgtools.read.Paths(**csiborgtools.paths_glamdring) paths = csiborgtools.read.Paths(**csiborgtools.paths_glamdring)
nsnap = max(paths.get_snapshots(nsim)) nsnap = max(paths.get_snapshots(nsim, "csiborg"))
fpath = paths.fof_membership(nsim) fpath = paths.fof_membership(nsim, "csiborg")
if verbose: if verbose:
print(f"{datetime.now()}: loading from ... `{fpath}`.") print(f"{datetime.now()}: loading from ... `{fpath}`.")
# Columns are halo ID, particle ID. # Columns are halo ID, particle ID.
fof = numpy.load(fpath) fof = numpy.load(fpath)
reader = csiborgtools.read.ParticleReader(paths) reader = csiborgtools.read.CSiBORGReader(paths)
pars_extract = ["x"] # Dummy variable pars_extract = ["x"] # Dummy variable
__, pids = reader.read_particle(nsnap, nsim, pars_extract, __, pids = reader.read_particle(nsnap, nsim, pars_extract,
return_structured=False, verbose=verbose) return_structured=False, verbose=verbose)
@ -123,7 +123,7 @@ def sort_fofid(nsim, verbose=True):
hid, pid = fof[i] hid, pid = fof[i]
fof_hids[pids_idx[pid]] = hid fof_hids[pids_idx[pid]] = hid
fout = paths.fof_membership(nsim, sorted=True) fout = paths.fof_membership(nsim, "csiborg", sorted=True)
if verbose: if verbose:
print(f"Saving the sorted data to ... `{fout}`") print(f"Saving the sorted data to ... `{fout}`")
numpy.save(fout, fof_hids) numpy.save(fout, fof_hids)

View File

@ -58,10 +58,10 @@ for i, nsim in enumerate(nsims):
if rank == 0: if rank == 0:
now = datetime.now() now = datetime.now()
print(f"{now}: calculating {i}th simulation `{nsim}`.", flush=True) print(f"{now}: calculating {i}th simulation `{nsim}`.", flush=True)
nsnap = max(paths.get_snapshots(nsim)) nsnap = max(paths.get_snapshots(nsim, "csiborg"))
box = csiborgtools.read.CSiBORGBox(nsnap, nsim, paths) box = csiborgtools.read.CSiBORGBox(nsnap, nsim, paths)
f = csiborgtools.read.read_h5(paths.particles(nsim)) f = csiborgtools.read.read_h5(paths.particles(nsim, "csiborg"))
particles = f["particles"] particles = f["particles"]
clump_map = f["clumpmap"] clump_map = f["clumpmap"]
clid2map = {clid: i for i, clid in enumerate(clump_map[:, 0])} clid2map = {clid: i for i, clid in enumerate(clump_map[:, 0])}

View File

@ -38,7 +38,7 @@ mmain_reader = csiborgtools.read.MmainReader(paths)
def do_mmain(nsim): def do_mmain(nsim):
nsnap = max(paths.get_snapshots(nsim)) nsnap = max(paths.get_snapshots(nsim, "csiborg"))
# NOTE: currently works for highest snapshot anyway # NOTE: currently works for highest snapshot anyway
mmain, ultimate_parent = mmain_reader.make_mmain(nsim, verbose=False) mmain, ultimate_parent = mmain_reader.make_mmain(nsim, verbose=False)
numpy.savez(paths.mmain(nsnap, nsim), numpy.savez(paths.mmain(nsnap, nsim),

View File

@ -60,6 +60,11 @@ def minmax_halo(hid, halo_ids, start_loop=0):
return start, end return start, end
###############################################################################
# Sorting and dumping #
###############################################################################
def main(nsim, simname, verbose): def main(nsim, simname, verbose):
""" """
Read in the snapshot particles, sort them by their FoF halo ID and dump Read in the snapshot particles, sort them by their FoF halo ID and dump
@ -81,20 +86,21 @@ def main(nsim, simname, verbose):
None None
""" """
paths = csiborgtools.read.Paths(**csiborgtools.paths_glamdring) paths = csiborgtools.read.Paths(**csiborgtools.paths_glamdring)
partreader = csiborgtools.read.ParticleReader(paths) if simname == "csiborg":
partreader = csiborgtools.read.CSiBORGReader(paths)
else:
partreader = csiborgtools.read.QuijoteReader(paths)
if simname == "quijote": nsnap = max(paths.get_snapshots(nsim, simname))
raise NotImplementedError("Not implemented for Quijote yet.") fname = paths.particles(nsim, simname)
# Keep "ID" as the last column!
pars_extract = ['x', 'y', 'z', 'vx', 'vy', 'vz', 'M', "ID"]
nsnap = max(paths.get_snapshots(nsim))
fname = paths.particles(nsim)
# We first read in the halo IDs of the particles and infer the sorting. # We first read in the halo IDs of the particles and infer the sorting.
# Right away we dump the halo IDs to a HDF5 file and clear up memory. # Right away we dump the halo IDs to a HDF5 file and clear up memory.
if verbose: if verbose:
print(f"{datetime.now()}: loading particles {nsim}.", flush=True) print(f"{datetime.now()}: loading PIDs of IC {nsim}.", flush=True)
part_hids = partreader.read_fof_hids(nsim) part_hids = partreader.read_fof_hids(
nsnap=nsnap, nsim=nsim, verbose=verbose)
if verbose:
print(f"{datetime.now()}: sorting PIDs of IC {nsim}.", flush=True)
sort_indxs = numpy.argsort(part_hids).astype(numpy.int32) sort_indxs = numpy.argsort(part_hids).astype(numpy.int32)
part_hids = part_hids[sort_indxs] part_hids = part_hids[sort_indxs]
with h5py.File(fname, "w") as f: with h5py.File(fname, "w") as f:
@ -106,6 +112,10 @@ def main(nsim, simname, verbose):
# Next we read in the particles and sort them by their halo ID. # Next we read in the particles and sort them by their halo ID.
# We cannot directly read this as an unstructured array because the float32 # We cannot directly read this as an unstructured array because the float32
# precision is insufficient to capture the halo IDs. # precision is insufficient to capture the halo IDs.
if simname == "csiborg":
pars_extract = ['x', 'y', 'z', 'vx', 'vy', 'vz', 'M', "ID"]
else:
pars_extract = None
parts, pids = partreader.read_particle( parts, pids = partreader.read_particle(
nsnap, nsim, pars_extract, return_structured=False, verbose=verbose) nsnap, nsim, pars_extract, return_structured=False, verbose=verbose)
# Now we in two steps save the particles and particle IDs. # Now we in two steps save the particles and particle IDs.
@ -129,11 +139,11 @@ def main(nsim, simname, verbose):
collect() collect()
if verbose: if verbose:
print(f"{datetime.now()}: creating halo map for {nsim}.", flush=True) print(f"{datetime.now()}: creating a halo map for {nsim}.", flush=True)
# Load clump IDs back to memory # Load clump IDs back to memory
with h5py.File(fname, "r") as f: with h5py.File(fname, "r") as f:
part_hids = f["halo_ids"][:] part_hids = f["halo_ids"][:]
# We loop over the unique clump IDs. # We loop over the unique halo IDs.
unique_halo_ids = numpy.unique(part_hids) unique_halo_ids = numpy.unique(part_hids)
halo_map = numpy.full((unique_halo_ids.size, 3), numpy.nan, halo_map = numpy.full((unique_halo_ids.size, 3), numpy.nan,
dtype=numpy.int32) dtype=numpy.int32)
@ -148,7 +158,7 @@ def main(nsim, simname, verbose):
start_loop = kf start_loop = kf
# We save the mapping to a HDF5 file # We save the mapping to a HDF5 file
with h5py.File(paths.particles(nsim), "r+") as f: with h5py.File(fname, "r+") as f:
f.create_dataset("halomap", data=halo_map) f.create_dataset("halomap", data=halo_map)
f.close() f.close()

View File

@ -50,34 +50,55 @@ def _main(nsim, simname, verbose):
verbose : bool verbose : bool
Verbosity flag. Verbosity flag.
""" """
if simname == "quijote":
raise NotImplementedError("Quijote not implemented yet.")
paths = csiborgtools.read.Paths(**csiborgtools.paths_glamdring) paths = csiborgtools.read.Paths(**csiborgtools.paths_glamdring)
partreader = csiborgtools.read.ParticleReader(paths) if simname == "csiborg":
partreader = csiborgtools.read.CSiBORGReader(paths)
else:
partreader = csiborgtools.read.QuijoteReader(paths)
if verbose: if verbose:
print(f"{datetime.now()}: reading and processing simulation {nsim}.", print(f"{datetime.now()}: reading and processing simulation `{nsim}`.",
flush=True) flush=True)
# We first load the particle IDs in the final snapshot. # We first load the particle IDs in the final snapshot.
pidf = csiborgtools.read.read_h5(paths.particles(nsim)) pidf = csiborgtools.read.read_h5(paths.particles(nsim, simname))
pidf = pidf["particle_ids"] pidf = pidf["particle_ids"]
# Then we load the particles in the initil snapshot and make sure that # Then we load the particles in the initil snapshot and make sure that
# their particle IDs are sorted as in the final snapshot. Again, because of # their particle IDs are sorted as in the final snapshot. Again, because of
# precision this must be read as structured. # precision this must be read as structured.
# NOTE: ID has to be the last column. if simname == "csiborg":
pars_extract = ["x", "y", "z", "M", "ID"] pars_extract = ["x", "y", "z", "M", "ID"]
# CSiBORG's initial snapshot ID
nsnap = 1
else:
pars_extract = None
# Use this to point the reader to the ICs snapshot
nsnap = -1
part0, pid0 = partreader.read_particle( part0, pid0 = partreader.read_particle(
1, nsim, pars_extract, return_structured=False, verbose=verbose) nsnap, nsim, pars_extract, return_structured=False, verbose=verbose)
# Quijote's initial snapshot information also contains velocities but we
# don't need those.
if simname == "quijote":
part0 = part0[:, [0, 1, 2, 6]]
# In Quijote some particles are position precisely at the edge of the
# box. Move them to be just inside.
pos = part0[:, :3]
mask = pos >= 1
if numpy.any(mask):
spacing = numpy.spacing(pos[mask])
assert numpy.max(spacing) <= 1e-5
pos[mask] -= spacing
# First enforce them to already be sorted and then apply reverse # First enforce them to already be sorted and then apply reverse
# sorting from the final snapshot. # sorting from the final snapshot.
part0 = part0[numpy.argsort(pid0)] part0 = part0[numpy.argsort(pid0)]
del pid0 del pid0
collect() collect()
part0 = part0[numpy.argsort(numpy.argsort(pidf))] part0 = part0[numpy.argsort(numpy.argsort(pidf))]
fout = paths.initmatch(nsim, simname, "particles")
if verbose: if verbose:
print(f"{datetime.now()}: dumping particles for {nsim}.", flush=True) print(f"{datetime.now()}: dumping particles for `{nsim}` to `{fout}`",
with h5py.File(paths.initmatch(nsim, "particles"), "w") as f: flush=True)
with h5py.File(fout, "w") as f:
f.create_dataset("particles", data=part0) f.create_dataset("particles", data=part0)

View File

@ -352,7 +352,7 @@ def plot_projected_field(kind, nsim, grid, in_rsp, smooth_scale, MAS="PCS",
""" """
print(f"Plotting projected field `{kind}`. ", flush=True) print(f"Plotting projected field `{kind}`. ", flush=True)
paths = csiborgtools.read.Paths(**csiborgtools.paths_glamdring) paths = csiborgtools.read.Paths(**csiborgtools.paths_glamdring)
nsnap = max(paths.get_snapshots(nsim)) nsnap = max(paths.get_snapshots(nsim, "csiborg"))
box = csiborgtools.read.CSiBORGBox(nsnap, nsim, paths) box = csiborgtools.read.CSiBORGBox(nsnap, nsim, paths)
if kind == "overdensity": if kind == "overdensity":
@ -563,10 +563,10 @@ def plot_sky_distribution(field, nsim, grid, nside, smooth_scale=None,
Whether to save the figure as a pdf. Whether to save the figure as a pdf.
""" """
paths = csiborgtools.read.Paths(**csiborgtools.paths_glamdring) paths = csiborgtools.read.Paths(**csiborgtools.paths_glamdring)
nsnap = max(paths.get_snapshots(nsim)) nsnap = max(paths.get_snapshots(nsim, "csiborg"))
box = csiborgtools.read.CSiBORGBox(nsnap, nsim, paths) box = csiborgtools.read.CSiBORGBox(nsnap, nsim, paths)
if kind == "overdensity": if field== "overdensity":
field = load_field("density", nsim, grid, MAS=MAS, in_rsp=False, field = load_field("density", nsim, grid, MAS=MAS, in_rsp=False,
smooth_scale=smooth_scale) smooth_scale=smooth_scale)
density_gen = csiborgtools.field.DensityField(box, MAS) density_gen = csiborgtools.field.DensityField(box, MAS)
@ -658,7 +658,8 @@ if __name__ == "__main__":
if False: if False:
paths = csiborgtools.read.Paths(**csiborgtools.paths_glamdring) paths = csiborgtools.read.Paths(**csiborgtools.paths_glamdring)
d = csiborgtools.read.read_h5(paths.particles(7444))["particles"] d = csiborgtools.read.read_h5(paths.particles(7444, "csiborg"))
d = d["particles"]
plt.figure() plt.figure()
plt.hist(d[:100000, 4], bins="auto") plt.hist(d[:100000, 4], bins="auto")