CDF for nearest neighbour (#63)

* Updat ebounds

* fix mistake

* add plot script

* fix which sims

* Add Poisson

* Just docs

* Hide things to __main__

* Rename paths

* Move old script

* Remove radpos

* Paths renaming

* Paths renaming

* Remove trunk stuff

* Add import

* Add nearest neighbour search

* Add Quijote fiducial indices

* Add final snapshot matching

* Add fiducial observer selection

* add boxsizes

* Add reading functions

* Little stuff

* Bring back the fiducial observer

* Add arguments

* Add quijote paths

* Add notes

* Get this running

* Add yaml

* Remove Poisson stuff

* Get the 2PCF script running

* Add not finished htings

* Remove comment

* Verbosity only on 0th rank!

* Update plotting style

* Add nearest neighbour CDF

* Save radial distance too

* Add centres

* Add basic plotting
This commit is contained in:
Richard Stiskalek 2023-05-21 22:46:28 +01:00 committed by GitHub
parent 369438f881
commit 2185846e90
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 1254 additions and 351 deletions

View file

@ -241,7 +241,7 @@ class kNN_1DCDF:
def __call__(self, knn, rvs_gen, nneighbours, nsamples, rmin, rmax, neval,
batch_size=None, random_state=42, dtype=numpy.float32):
"""
Calculate the CDF for a set of kNNs of CSiBORG halo catalogues.
Calculate the CDF for a set of kNNs of halo catalogues.
Parameters
----------

View file

@ -15,5 +15,6 @@
from .match import (ParticleOverlap, RealisationsMatcher, # noqa
calculate_overlap, calculate_overlap_indxs,
cosine_similarity)
from .nearest_neighbour import find_neighbour # noqa
from .num_density import binned_counts, number_density # noqa
from .utils import concatenate_parts # noqa

View file

@ -0,0 +1,56 @@
# Copyright (C) 2022 Richard Stiskalek
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""
Tools for finding the nearest neighbours of reference simulation haloes from
cross simulations.
"""
import numpy
def find_neighbour(nsim0, cats):
"""
Find the nearest neighbour of halos in `cat0` in `catx`.
Parameters
----------
nsim0 : int
Index of the reference simulation.
cats : dict
Dictionary of halo catalogues. Keys must be the simulation indices.
Returns
-------
dists : 2-dimensional array of shape `(nhalos, len(cats) - 1)`
Distances to the nearest neighbour.
cross_hindxs : 2-dimensional array of shape `(nhalos, len(cats) - 1)`
Halo indices of the nearest neighbour.
"""
cat0 = cats[nsim0]
X = cat0.position(in_initial=False)
shape = (X.shape[0], len(cats) - 1)
dists = numpy.full(shape, numpy.nan, dtype=numpy.float32)
cross_hindxs = numpy.full(shape, numpy.nan, dtype=numpy.int32)
i = 0
for nsimx, catx in cats.items():
if nsimx == nsim0:
continue
dist, ind = catx.nearest_neighbours(X, radius=1, in_initial=False,
knearest=True)
dists[:, i] = dist.reshape(-1,)
cross_hindxs[:, i] = catx["index"][ind.reshape(-1,)]
i += 1
return dists, cross_hindxs

View file

@ -16,6 +16,7 @@ from .box_units import CSiBORGBox, QuijoteBox # noqa
from .halo_cat import (ClumpsCatalogue, HaloCatalogue, # noqa
QuijoteHaloCatalogue, fiducial_observers)
from .knn_summary import kNNCDFReader # noqa
from .nearest_neighbour_summary import NearestNeighbourReader # noqa
from .obs import (SDSS, MCXCClusters, PlanckClusters, TwoMPPGalaxies, # noqa
TwoMPPGroups)
from .overlap_summary import (NPairsOverlap, PairOverlap, # noqa

View file

@ -15,7 +15,7 @@
"""
Simulation box unit transformations.
"""
from abc import ABC
from abc import ABC, abstractproperty
import numpy
from astropy import constants, units
@ -93,6 +93,17 @@ class BaseBox(ABC):
"""
return self.cosmo.Om0
@abstractproperty
def boxsize(self):
"""
Box size in cMpc.
Returns
-------
boxsize : float
"""
pass
###############################################################################
# CSiBORG box #
@ -383,6 +394,10 @@ class CSiBORGBox(BaseBox):
return data
@property
def boxsize(self):
return self.box2mpc(1.)
###############################################################################
# Quijote fiducial cosmology box #
@ -408,3 +423,7 @@ class QuijoteBox(BaseBox):
self._cosmo = LambdaCDM(H0=67.11, Om0=0.3175, Ode0=0.6825,
Tcmb0=2.725 * units.K, Ob0=0.049)
@property
def boxsize(self):
return 1000. / (self._cosmo.H0.value / 100)

View file

@ -439,11 +439,11 @@ class ClumpsCatalogue(BaseCSiBORG):
cols = ["index", "parent", "x", "y", "z", "mass_cl"]
self._data = partreader.read_clumps(self.nsnap, self.nsim, cols=cols)
# Overwrite the parent with the ultimate parent
mmain = numpy.load(self.paths.mmain_path(self.nsnap, self.nsim))
mmain = numpy.load(self.paths.mmain(self.nsnap, self.nsim))
self._data["parent"] = mmain["ultimate_parent"]
if load_fitted:
fits = numpy.load(paths.structfit_path(self.nsnap, nsim, "clumps"))
fits = numpy.load(paths.structfit(self.nsnap, nsim, "clumps"))
cols = [col for col in fits.dtype.names if col != "index"]
X = [fits[col] for col in cols]
self._data = add_columns(self._data, X, cols)
@ -512,20 +512,20 @@ class HaloCatalogue(BaseCSiBORG):
self.nsim = nsim
self.paths = paths
# Read in the mmain catalogue of summed substructure
mmain = numpy.load(self.paths.mmain_path(self.nsnap, self.nsim))
mmain = numpy.load(self.paths.mmain(self.nsnap, self.nsim))
self._data = mmain["mmain"]
# We will also need the clumps catalogue
if load_clumps_cat:
self._clumps_cat = ClumpsCatalogue(nsim, paths, rawdata=True,
load_fitted=False)
if load_fitted:
fits = numpy.load(paths.structfit_path(self.nsnap, nsim, "halos"))
fits = numpy.load(paths.structfit(self.nsnap, nsim, "halos"))
cols = [col for col in fits.dtype.names if col != "index"]
X = [fits[col] for col in cols]
self._data = add_columns(self._data, X, cols)
if load_initial:
fits = numpy.load(paths.initmatch_path(nsim, "fit"))
fits = numpy.load(paths.initmatch(nsim, "fit"))
X, cols = [], []
for col in fits.dtype.names:
if col == "index":
@ -590,8 +590,7 @@ class QuijoteHaloCatalogue(BaseCatalogue):
Snapshot index.
origin : len-3 tuple, optional
Where to place the origin of the box. By default the centre of the box.
In units of :math:`cMpc`. Optionally can be an integer between 0 and 8,
inclusive to correspond to CSiBORG boxes.
In units of :math:`cMpc`.
bounds : dict
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
@ -601,12 +600,16 @@ class QuijoteHaloCatalogue(BaseCatalogue):
Keyword arguments for backward compatibility.
"""
_nsnap = None
_origin = None
def __init__(self, nsim, paths, nsnap,
origin=[500 / 0.6711, 500 / 0.6711, 500 / 0.6711],
bounds=None, **kwargs):
self.paths = paths
self.nsnap = nsnap
self.origin = origin
self._boxwidth = 1000 / 0.6711
fpath = join(self.paths.quijote_dir, "halos", str(nsim))
fof = FoF_catalog(fpath, self.nsnap, long_ids=False, swap=False,
SFR=False, read_IDs=False)
@ -614,21 +617,18 @@ class QuijoteHaloCatalogue(BaseCatalogue):
cols = [("x", numpy.float32), ("y", numpy.float32),
("z", numpy.float32), ("vx", numpy.float32),
("vy", numpy.float32), ("vz", numpy.float32),
("group_mass", numpy.float32), ("npart", numpy.int32)]
("group_mass", numpy.float32), ("npart", numpy.int32),
("index", numpy.int32)]
data = cols_to_structured(fof.GroupLen.size, cols)
if isinstance(origin, int):
origin = fiducial_observers(1000 / 0.6711, 155.5 / 0.6711)[origin]
pos = fof.GroupPos / 1e3 / self.box.h
for i in range(3):
pos[:, i] -= origin[i]
vel = fof.GroupVel * (1 + self.redshift)
for i, p in enumerate(["x", "y", "z"]):
data[p] = pos[:, i]
data[p] = pos[:, i] - self.origin[i]
data["v" + p] = vel[:, i]
data["group_mass"] = fof.GroupMass * 1e10 / self.box.h
data["npart"] = fof.GroupLen
data["index"] = numpy.arange(data.size, dtype=numpy.int32)
self._data = data
if bounds is not None:
@ -673,6 +673,53 @@ class QuijoteHaloCatalogue(BaseCatalogue):
"""
return QuijoteBox(self.nsnap)
@property
def origin(self):
"""
Origin of the box with respect to the initial box units.
Returns
-------
origin : len-3 tuple
"""
if self._origin is None:
raise ValueError("`origin` is not set.")
return self._origin
@origin.setter
def origin(self, origin):
if isinstance(origin, (list, tuple)):
origin = numpy.asanyarray(origin)
assert origin.ndim == 1 and origin.size == 3
self._origin = origin
def pick_fiducial_observer(self, n, rmax):
r"""
Return a copy of itself, storing only halos within `rmax` of the new
fiducial observer.
Parameters
----------
n : int
Fiducial observer index.
rmax : float
Maximum distance from the fiducial observer in :math:`cMpc`.
Returns
-------
cat : instance of csiborgtools.read.QuijoteHaloCatalogue
"""
new_origin = fiducial_observers(self.box.boxsize, rmax)[n]
# We make a copy of the catalogue to avoid modifying the original.
# Then, we shift coordinates back to the original box frame and then to
# the new origin.
cat = deepcopy(self)
for i, p in enumerate(('x', 'y', 'z')):
cat._data[p] += self.origin[i]
cat._data[p] -= new_origin[i]
cat.apply_bounds({"dist": (0, rmax)})
return cat
###############################################################################
# Utility functions for halo catalogues #

View file

@ -46,13 +46,15 @@ class kNNCDFReader:
def paths(self, paths):
self._paths = paths
def read(self, run, kind, rmin=None, rmax=None, to_clip=True):
def read(self, simname, run, kind, rmin=None, rmax=None, to_clip=True):
"""
Read the auto- or cross-correlation kNN-CDF data. Infers the type from
the data files.
Parameters
----------
simname : str
Simulation name. Must be either `csiborg` or `quijote`.
run : str
Run ID to read in.
kind : str
@ -71,14 +73,17 @@ class kNNCDFReader:
Separations where the CDF is evaluated.
out : 3-dimensional array of shape `(len(files), len(ks), neval)`
Array of CDFs or cross-correlations.
ndensity : 1-dimensional array of shape `(len(files), )`
Number density of halos in the simulation.
"""
assert kind in ["auto", "cross"]
assert simname in ["csiborg", "quijote"]
if kind == "auto":
files = self.paths.knnauto_path(run)
files = self.paths.knnauto(simname, run)
else:
files = self.paths.knncross_path(run)
files = self.paths.knncross(simname, run)
if len(files) == 0:
raise RuntimeError("No files found for run `{}`.".format(run))
raise RuntimeError(f"No files found for run `{run}`.")
for i, file in enumerate(files):
data = joblib.load(file)
@ -91,8 +96,11 @@ class kNNCDFReader:
isauto = True
out = numpy.full((len(files), *data[kind].shape), numpy.nan,
dtype=numpy.float32)
ndensity = numpy.full(len(files), numpy.nan,
dtype=numpy.float32)
rs = data["rs"]
out[i, ...] = data[kind]
ndensity[i] = data["ndensity"]
if isauto and to_clip:
out[i, ...] = self.clipped_cdf(out[i, ...])
@ -103,7 +111,7 @@ class kNNCDFReader:
rs = rs[mask]
out = out[..., mask]
return rs, out
return rs, out, ndensity
@staticmethod
def peaked_cdf(cdf, make_copy=True):
@ -191,6 +199,7 @@ class kNNCDFReader:
----------
cdf : 3-dimensional array of shape `(len(files), len(ks), len(rs))`
Array of CDFs
Returns
-------
out : 3-dimensional array of shape `(len(ks) - 1, len(rs), 2)`
@ -212,16 +221,33 @@ class kNNCDFReader:
----------
rs : 1-dimensional array
Array of separations.
k : int
k : int or 1-dimensional array
Number of objects.
ndensity : float
ndensity : float or 1-dimensional array
Number density of objects.
Returns
-------
pk : 1-dimensional array
pk : 1-dimensional array or 3-dimensional array
The PDF that a spherical volume of radius :math:`r` contains
:math:`k` objects.
:math:`k` objects. If `k` and `ndensity` are both arrays, the shape
is `(len(ndensity), len(k), len(rs))`.
"""
V = 4 * numpy.pi / 3 * rs**3
return (ndensity * V)**k / factorial(k) * numpy.exp(-ndensity * V)
def probk(k, ndensity):
return (ndensity * V)**k / factorial(k) * numpy.exp(-ndensity * V)
if isinstance(k, int) and isinstance(ndensity, float):
return probk(k, ndensity)
# If either k or ndensity is an array, make sure the other is too.
assert isinstance(k, numpy.ndarray) and k.ndim == 1
assert isinstance(ndensity, numpy.ndarray) and ndensity.ndim == 1
out = numpy.full((ndensity.size, k.size, rs.size), numpy.nan,
dtype=numpy.float32)
for i in range(ndensity.size):
for j in range(k.size):
out[i, j, :] = probk(k[j], ndensity[i])
return out

View file

@ -0,0 +1,287 @@
# Copyright (C) 2023 Richard Stiskalek
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""
Nearest neighbour summary for assessing goodness-of-reconstruction of a halo in
the final snapshot.
"""
from math import floor
import numpy
from numba import jit
from tqdm import tqdm
class NearestNeighbourReader:
"""
Shortcut object to read in nearest neighbour data for assessing the
goodness-of-reconstruction of a halo in the final snapshot.
Parameters
----------
rmax_radial : float
Radius of the high-resolution region.
paths : py:class`csiborgtools.read.Paths`
Paths object.
TODO: docs
"""
_paths = None
_rmax_radial = None
_nbins_radial = None
_rmax_neighbour = None
_nbins_neighbour = None
def __init__(self, rmax_radial, nbins_radial, rmax_neighbour,
nbins_neighbour, paths, **kwargs):
self.paths = paths
self.rmax_radial = rmax_radial
self.nbins_radial = nbins_radial
self.rmax_neighbour = rmax_neighbour
self.nbins_neighbour = nbins_neighbour
@property
def rmax_radial_radial(self):
"""
Radius of the high-resolution region.
Parameters
----------
rmax_radial_radial : float
"""
return self._rmax_radial_radial
@rmax_radial_radial.setter
def rmax_radial_radial(self, rmax_radial_radial):
assert isinstance(rmax_radial_radial, float)
self._rmax_radial_radial = rmax_radial_radial
@property
def paths(self):
"""
Paths manager.
Parameters
----------
paths : py:class`csiborgtools.read.Paths`
"""
return self._paths
@property
def nbins_radial(self):
"""
Number radial of bins.
Returns
-------
nbins_radial : int
"""
return self._nbins_radial
@nbins_radial.setter
def nbins_radial(self, nbins_radial):
assert isinstance(nbins_radial, int)
self._nbins_radial = nbins_radial
@property
def nbins_neighbour(self):
"""
Number of neighbour bins.
Returns
-------
nbins_neighbour : int
"""
return self._nbins_neighbour
@nbins_neighbour.setter
def nbins_neighbour(self, nbins_neighbour):
assert isinstance(nbins_neighbour, int)
self._nbins_neighbour = nbins_neighbour
@property
def rmax_neighbour(self):
"""
Maximum neighbour distance.
Returns
-------
rmax_neighbour : float
"""
return self._rmax_neighbour
@rmax_neighbour.setter
def rmax_neighbour(self, rmax_neighbour):
assert isinstance(rmax_neighbour, float)
self._rmax_neighbour = rmax_neighbour
@paths.setter
def paths(self, paths):
self._paths = paths
@property
def radial_bin_edges(self):
"""
Radial bins.
Returns
-------
radial_bins : 1-dimensional array
"""
nbins = self.nbins_radial + 1
return self.rmax_radial * numpy.linspace(0, 1, nbins)**(1./3)
@property
def neighbour_bin_edges(self):
"""
Neighbour bins edges
Returns
-------
neighbour_bins : 1-dimensional array
"""
nbins = self.nbins_neighbour + 1
return numpy.linspace(0, self.rmax_neighbour, nbins)
def bin_centres(self, kind):
"""
Bin centres. Either for `radial` or `neighbour` bins.
Parameters
----------
kind : str
Bin kind. Either `radial` or `neighbour`.
Returns
-------
bin_centres : 1-dimensional array
"""
assert kind in ["radial", "neighbour"]
if kind == "radial":
edges = self.radial_bin_edges
else:
edges = self.neighbour_bin_edges
return 0.5 * (edges[1:] + edges[:-1])
def read_single(self, simname, run, nsim, nobs=None):
"""
Read in the nearest neighbour distances for halos from a single
simulation.
Parameters
----------
simname : str
Simulation name. Must be either `csiborg` or `quijote`.
run : str
Run name.
nsim : int
Simulation index.
nobs : int, optional
Fiducial Quijote observer index.
Returns
-------
data : numpy archive
Archive with keys `ndist`, `rdist`, `mass`, `cross_hindxs``
"""
assert simname in ["csiborg", "quijote"]
fpath = self.paths.cross_nearest(simname, run, nsim, nobs)
return numpy.load(fpath)
def build_cdf(self, simname, run, verbose=True):
"""
Build the CDF for the nearest neighbour distribution. Counts the binned
number of neighbour for each halo as a funtion of its radial distance
from the centre of the high-resolution region and converts it to a CDF.
Parameters
----------
simname : str
Simulation name. Must be either `csiborg` or `quijote`.
run : str
Run name.
verbose : bool, optional
Verbosity flag.
Returns
-------
cdf : 2-dimensional array of shape `(nbins_radial, nbins_neighbour)`
"""
assert simname in ["csiborg", "quijote"]
rbin_edges = self.radial_bin_edges
# We first bin the distances as a function of each reference halo
# radial distance and then its nearest neighbour distance.
fpaths = self.paths.cross_nearest(simname, run)
out = numpy.zeros((self.nbins_radial, self.nbins_neighbour),
dtype=numpy.float32)
for fpath in tqdm(fpaths) if verbose else fpaths:
data = numpy.load(fpath)
out = count_neighbour(
out, data["ndist"], data["rdist"], rbin_edges,
self.rmax_neighbour, self.nbins_neighbour)
# We then build up a CDF for each radial bin.
out = numpy.cumsum(out, axis=1, out=out)
out /= out[:, -1].reshape(-1, 1)
return out
###############################################################################
# Support functions #
###############################################################################
@jit(nopython=True)
def count_neighbour(counts, ndist, rdist, rbin_edges, rmax_neighbour,
nbins_neighbour):
"""
Count the number of neighbour in neighbours bins for each halo as a funtion
of its radial distance from the centre of the high-resolution region.
Parameters
----------
counts : 2-dimensional array of shape `(nbins_radial, nbins_neighbour)`
Array to store the counts.
ndist : 2-dimensional array of shape `(nhalos, ncross_simulations)`
Distance of each halo to its nearest neighbour from a cross simulation.
rdist : 1-dimensional array of shape `(nhalos, )`
Distance of each halo to the centre of the high-resolution region.
rbin_edges : 1-dimensional array of shape `(nbins_radial + 1, )`
Edges of the radial bins.
rmax_neighbour : float
Maximum neighbour distance.
nbins_neighbour : int
Number of neighbour bins.
Returns
-------
counts : 2-dimensional array of shape `(nbins_radial, nbins_neighbour)`
"""
ncross = ndist.shape[1]
# We normalise the neighbour distance by the maximum neighbour distance and
# multiply by the number of bins. This way, the floor of each distance is
# the bin number.
ndist /= rmax_neighbour
ndist *= nbins_neighbour
# We loop over each halo, assign it to a radial bin and then assign its
# neighbours to bins.
for i, radial_cell in enumerate(numpy.digitize(rdist, rbin_edges) - 1):
for j in range(ncross):
neighbour_cell = floor(ndist[i, j])
if neighbour_cell < nbins_neighbour:
counts[radial_cell, neighbour_cell] += 1
return counts

View file

@ -71,8 +71,8 @@ class PairOverlap:
# We first load in the output files. We need to find the right
# combination of the reference and cross simulation.
fname = paths.overlap_path(nsim0, nsimx, smoothed=False)
fname_inv = paths.overlap_path(nsimx, nsim0, smoothed=False)
fname = paths.overlap(nsim0, nsimx, smoothed=False)
fname_inv = paths.overlap(nsimx, nsim0, smoothed=False)
if isfile(fname):
data_ngp = numpy.load(fname, allow_pickle=True)
to_invert = False
@ -83,7 +83,7 @@ class PairOverlap:
else:
raise FileNotFoundError(f"No file found for {nsim0} and {nsimx}.")
fname_smooth = paths.overlap_path(cat0.nsim, catx.nsim, smoothed=True)
fname_smooth = paths.overlap(cat0.nsim, catx.nsim, smoothed=True)
data_smooth = numpy.load(fname_smooth, allow_pickle=True)
# Create mapping from halo indices to array positions in the catalogue.
@ -628,11 +628,11 @@ def get_cross_sims(nsim0, paths, smoothed):
Whether to use the smoothed overlap or not.
"""
nsimxs = []
for nsimx in paths.get_ics():
for nsimx in paths.get_ics("csiborg"):
if nsimx == nsim0:
continue
f1 = paths.overlap_path(nsim0, nsimx, smoothed)
f2 = paths.overlap_path(nsimx, nsim0, smoothed)
f1 = paths.overlap(nsim0, nsimx, smoothed)
f2 = paths.overlap(nsimx, nsim0, smoothed)
if isfile(f1) or isfile(f2):
nsimxs.append(nsimx)
return nsimxs

View file

@ -88,13 +88,6 @@ class Paths:
self._check_directory(path)
self._quijote_dir = path
@staticmethod
def get_quijote_ics():
"""
Quijote IC realisation IDs.
"""
return numpy.arange(100, dtype=int)
@property
def postdir(self):
"""
@ -130,9 +123,32 @@ class Paths:
warn(f"Created directory `{fpath}`.", UserWarning, stacklevel=1)
return fpath
def mmain_path(self, nsnap, nsim):
@staticmethod
def quijote_fiducial_nsim(nsim, nobs=None):
"""
Path to the `mmain` files summed substructure files.
Fiducial Quijote simulation ID. Combines the IC realisation and
observer placement.
Parameters
----------
nsim : int
IC realisation index.
nobs : int, optional
Fiducial observer index.
Returns
-------
id : str
"""
if nobs is None:
assert isinstance(nsim, str)
assert len(nsim) == 5
return nsim
return f"{str(nobs).zfill(2)}{str(nsim).zfill(3)}"
def mmain(self, nsnap, nsim):
"""
Path to the `mmain` CSiBORG files of summed substructure.
Parameters
----------
@ -152,10 +168,10 @@ class Paths:
return join(fdir,
f"mmain_{str(nsim).zfill(5)}_{str(nsnap).zfill(5)}.npz")
def initmatch_path(self, nsim, kind):
def initmatch(self, nsim, kind):
"""
Path to the `initmatch` files where the clump match between the
initial and final snapshot is stored.
Path to the `initmatch` files where the halo match between the
initial and final snapshot of a CSiBORG realisaiton is stored.
Parameters
----------
@ -176,26 +192,35 @@ class Paths:
warn(f"Created directory `{fdir}`.", UserWarning, stacklevel=1)
return join(fdir, f"{kind}_{str(nsim).zfill(5)}.{ftype}")
def get_ics(self):
def get_ics(self, simname):
"""
Get CSiBORG IC realisation IDs from the list of folders in
`self.srcdir`.
Get available IC realisation IDs for either the CSiBORG or Quijote
simulation suite.
Parameters
----------
simname : str
Simulation name. Must be one of `["csiborg", "quijote"]`.
Returns
-------
ids : 1-dimensional array
"""
files = glob(join(self.srcdir, "ramses_out*"))
files = [f.split("/")[-1] for f in files] # Select only file names
files = [f for f in files if "_inv" not in f] # Remove inv. ICs
files = [f for f in files if "_new" not in f] # Remove _new
files = [f for f in files if "OLD" not in f] # Remove _old
ids = [int(f.split("_")[-1]) for f in files]
try:
ids.remove(5511)
except ValueError:
pass
return numpy.sort(ids)
assert simname in ["csiborg", "quijote"]
if simname == "csiborg":
files = glob(join(self.srcdir, "ramses_out*"))
files = [f.split("/")[-1] for f in files] # Only file names
files = [f for f in files if "_inv" not in f] # Remove inv. ICs
files = [f for f in files if "_new" not in f] # Remove _new
files = [f for f in files if "OLD" not in f] # Remove _old
ids = [int(f.split("_")[-1]) for f in files]
try:
ids.remove(5511)
except ValueError:
pass
return numpy.sort(ids)
else:
return numpy.arange(100, dtype=int)
def ic_path(self, nsim, tonew=False):
"""
@ -239,7 +264,7 @@ class Paths:
snaps = [int(snap.split("_")[-1].lstrip("0")) for snap in snaps]
return numpy.sort(snaps)
def snapshot_path(self, nsnap, nsim):
def snapshot(self, nsnap, nsim):
"""
Path to a CSiBORG IC realisation snapshot.
@ -258,9 +283,10 @@ class Paths:
simpath = self.ic_path(nsim, tonew=tonew)
return join(simpath, f"output_{str(nsnap).zfill(5)}")
def structfit_path(self, nsnap, nsim, kind):
def structfit(self, nsnap, nsim, kind):
"""
Path to the clump or halo catalogue from `fit_halos.py`.
Path to the clump or halo catalogue from `fit_halos.py`. Only CSiBORG
is supported.
Parameters
----------
@ -283,9 +309,9 @@ class Paths:
fname = f"{kind}_out_{str(nsim).zfill(5)}_{str(nsnap).zfill(5)}.npy"
return join(fdir, fname)
def overlap_path(self, nsim0, nsimx, smoothed):
def overlap(self, nsim0, nsimx, smoothed):
"""
Path to the overlap files between two simulations.
Path to the overlap files between two CSiBORG simulations.
Parameters
----------
@ -309,32 +335,10 @@ class Paths:
fname = fname.replace("overlap", "overlap_smoothed")
return join(fdir, fname)
def radpos_path(self, nsnap, nsim):
def particles(self, nsim):
"""
Path to the files containing radial positions of halo particles (with
summed substructure).
Parameters
----------
nsnap : int
Snapshot index.
nsim : int
IC realisation index.
Returns
-------
path : str
"""
fdir = join(self.postdir, "radpos")
if not isdir(fdir):
mkdir(fdir)
warn(f"Created directory `{fdir}`.", UserWarning, stacklevel=1)
fname = f"radpos_{str(nsim).zfill(5)}_{str(nsnap).zfill(5)}.npz"
return join(fdir, fname)
def particles_path(self, nsim):
"""
Path to the files containing all particles.
Path to the files containing all particles of a CSiBORG realisation at
:math:`z = 0`.
Parameters
----------
@ -352,9 +356,9 @@ class Paths:
fname = f"parts_{str(nsim).zfill(5)}.h5"
return join(fdir, fname)
def field_path(self, kind, MAS, grid, nsim, in_rsp):
def field(self, kind, MAS, grid, nsim, in_rsp):
"""
Path to the files containing the calculated density fields.
Path to the files containing the calculated density fields in CSiBORG.
Parameters
----------
@ -383,7 +387,43 @@ class Paths:
fname = f"{kind}_{MAS}_{str(nsim).zfill(5)}_grid{grid}.npy"
return join(fdir, fname)
def knnauto_path(self, simname, run, nsim=None, nobs=None):
def cross_nearest(self, simname, run, nsim=None, nobs=None):
"""
Path to the files containing distance from a halo in a reference
simulation to the nearest halo from a cross simulation.
Parameters
----------
simname : str
Simulation name. Must be one of: `csiborg`, `quijote`.
run : str
Run name.
nsim : int, optional
IC realisation index.
nobs : int, optional
Fiducial observer index in Quijote simulations.
Returns
-------
path : str
"""
assert simname in ["csiborg", "quijote"]
fdir = join(self.postdir, "nearest_neighbour")
if not isdir(fdir):
makedirs(fdir)
warn(f"Created directory `{fdir}`.", UserWarning, stacklevel=1)
if nsim is not None:
if simname == "csiborg":
nsim = str(nsim).zfill(5)
else:
nsim = self.quijote_fiducial_nsim(nsim, nobs)
return join(fdir, f"{simname}_nn_{nsim}_{run}.npz")
files = glob(join(fdir, f"{simname}_nn_*"))
run = "_" + run
return [f for f in files if run in f]
def knnauto(self, simname, run, nsim=None, nobs=None):
"""
Path to the `knn` auto-correlation files. If `nsim` is not specified
returns a list of files for this run for all available simulations.
@ -393,7 +433,7 @@ class Paths:
simname : str
Simulation name. Must be either `csiborg` or `quijote`.
run : str
Type of run.
Run name.
nsim : int, optional
IC realisation index.
nobs : int, optional
@ -412,15 +452,14 @@ class Paths:
if simname == "csiborg":
nsim = str(nsim).zfill(5)
else:
assert nobs is not None
nsim = f"{str(nobs).zfill(2)}{str(nsim).zfill(3)}"
nsim = self.quijote_fiducial_nsim(nsim, nobs)
return join(fdir, f"{simname}_knncdf_{nsim}_{run}.p")
files = glob(join(fdir, f"{simname}_knncdf*"))
run = "__" + run
run = "_" + run
return [f for f in files if run in f]
def knncross_path(self, simname, run, nsims=None):
def knncross(self, simname, run, nsims=None):
"""
Path to the `knn` cross-correlation files. If `nsims` is not specified
returns a list of files for this run for all available simulations.
@ -449,10 +488,10 @@ class Paths:
return join(fdir, f"{simname}_knncdf_{nsim0}_{nsimx}__{run}.p")
files = glob(join(fdir, f"{simname}_knncdf*"))
run = "__" + run
run = "_" + run
return [f for f in files if run in f]
def tpcfauto_path(self, simname, run, nsim=None):
def tpcfauto(self, simname, run, nsim=None):
"""
Path to the `tpcf` auto-correlation files. If `nsim` is not specified
returns a list of files for this run for all available simulations.

View file

@ -76,7 +76,7 @@ class ParticleReader:
Dictionary of information paramaters. Note that both keys and
values are strings.
"""
snappath = self.paths.snapshot_path(nsnap, nsim)
snappath = self.paths.snapshot(nsnap, nsim)
filename = join(snappath, "info_{}.txt".format(str(nsnap).zfill(5)))
with open(filename, "r") as f:
info = f.read().split()
@ -87,7 +87,6 @@ class ParticleReader:
keys = info[eqs - 1]
vals = info[eqs + 1]
# trunk-ignore(ruff/B905)
return {key: val for key, val in zip(keys, vals)}
def open_particle(self, nsnap, nsim, verbose=True):
@ -110,7 +109,7 @@ class ParticleReader:
partfiles : list of `scipy.io.FortranFile`
Opened part files.
"""
snappath = self.paths.snapshot_path(nsnap, nsim)
snappath = self.paths.snapshot(nsnap, nsim)
ncpu = int(self.read_info(nsnap, nsim)["ncpu"])
nsnap = str(nsnap).zfill(5)
if verbose:

View file

@ -65,7 +65,7 @@ class TPCFReader:
out : 2-dimensional array of shape `(len(files), len(rp))`
Array of 2PCFs.
"""
files = self.paths.tpcfauto_path(run)
files = self.paths.tpcfauto(run)
if len(files) == 0:
raise RuntimeError("No files found for run `{}`.".format(run[:-2]))