mirror of
https://github.com/Richard-Sti/csiborgtools_public.git
synced 2025-06-08 18:01:11 +00:00
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:
parent
369438f881
commit
2185846e90
34 changed files with 1254 additions and 351 deletions
|
@ -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
|
||||
----------
|
||||
|
|
|
@ -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
|
||||
|
|
56
csiborgtools/match/nearest_neighbour.py
Normal file
56
csiborgtools/match/nearest_neighbour.py
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 #
|
||||
|
|
|
@ -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
|
||||
|
|
287
csiborgtools/read/nearest_neighbour_summary.py
Normal file
287
csiborgtools/read/nearest_neighbour_summary.py
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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]))
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue