mirror of
https://github.com/Richard-Sti/csiborgtools_public.git
synced 2025-05-13 06:01:13 +00:00
Add pynbody and other support (#92)
* Simplify box units * Move old scripts * Add printing * Update readers * Disable boundscheck * Add new ordering * Clean up imports * Enforce dtype and add mass to quijote * Simplify print statements * Fix little typos * Fix key bug * Bug fixing * Delete boring comments * Improve ultimate clumps for PHEW * Delete boring comments * Add basic reading * Remove 0th index HID * Add flipping of X and Z * Updates to halo catalogues * Add ordered caching * Fix flipping * Add new flags * Fix PHEW empty clumps * Stop over-wrriting * Little improvements to angular neighbours * Add catalogue masking * Change if-else statements * Cache only filtered data * Add PHEW cats * Add comments * Sort imports * Get Quijote workign * Docs * Add HMF calculation * Move to old * Fix angular * Add great circle distance * Update imports * Update impotrts * Update docs * Remove unused import * Fix a quick bug * Update compatibility * Rename files * Renaming * Improve compatiblity * Rename snapsht * Fix snapshot bug * Update interface * Finish updating interface * Update all paths * Add old scripts * Add basic halo * Update imports * Improve snapshot processing * Update ordering * Fix how CM positions accessed * Add merger paths * Add imports * Add merger reading * Add making a merger tree * Add a basic merger tree reader * Add imports * Add main branch walking + comments + debuggin * Get tree running * Add working merger tree walking along main branch * Add units conversion for merger data * Add hid_to_array_index * Update merger tree * Add mergertree mass to PHEWcat * Edit comments * Add this to track changes... * Fix a little bug * Add mergertree mass * Add cache clearing * Improve summing substructure code * Littbe bug * Little updates to the merger tree reader * Update .giignore * Add box selection * Add optional deletingf of a group * add to keep track of changes * Update changes * Remove * Add manual tracker * Fix bug * Add m200c_to_r200c * Add manual halo tracking * Remove skipped snapshots * update cosmo params to match csiborg * remove old comments * Add SDSSxALFALFA * Fix bugs * Rename * Edit paths * Updates * Add comments * Add comment * Add hour conversion * Add imports * Add new observation class * Add selection * Add imports * Fix small bug * Add field copying for safety * Add matching to survey without masking * Add P(k) calculation * Add nb * Edit comment * Move files * Remove merger import * Edit setup.yp * Fix typo * Edit import warnigns * update nb * Update README * Update README * Update README * Add skeleton * Add skeleton
This commit is contained in:
parent
5500fbd2b9
commit
e972f8e3f2
53 changed files with 4627 additions and 1774 deletions
|
@ -12,12 +12,12 @@
|
|||
# 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.
|
||||
from csiborgtools import clustering, field, match, read, summary # noqa
|
||||
|
||||
from .utils import (center_of_mass, delta2ncells, number_counts, # noqa
|
||||
periodic_distance, periodic_distance_two_points, # noqa
|
||||
binned_statistic, cosine_similarity) # noqa
|
||||
from csiborgtools import clustering, field, halo, match, read, summary # noqa
|
||||
|
||||
from .utils import (center_of_mass, delta2ncells, number_counts, # noqa
|
||||
periodic_distance, periodic_distance_two_points, # noqa
|
||||
binned_statistic, cosine_similarity, fprint, # noqa
|
||||
hms_to_degrees, dms_to_degrees, great_circle_distance) # noqa
|
||||
|
||||
# Arguments to csiborgtools.read.Paths.
|
||||
paths_glamdring = {"srcdir": "/mnt/extraspace/hdesmond/",
|
||||
|
@ -46,5 +46,34 @@ class SDSS:
|
|||
(lambda x: cls[x] < 155, ("DIST", ))
|
||||
]
|
||||
|
||||
def __call__(self):
|
||||
return read.SDSS(h=1, sel_steps=self.steps)
|
||||
def __call__(self, fpath=None, apply_selection=True):
|
||||
if fpath is None:
|
||||
fpath = "/mnt/extraspace/rstiskalek/catalogs/nsa_v1_0_1.fits"
|
||||
sel_steps = self.steps if apply_selection else None
|
||||
return read.SDSS(fpath, h=1, sel_steps=sel_steps)
|
||||
|
||||
|
||||
class SDSSxALFALFA:
|
||||
@staticmethod
|
||||
def steps(cls):
|
||||
return [(lambda x: cls[x], ("IN_DR7_LSS",)),
|
||||
(lambda x: cls[x] < 17.6, ("ELPETRO_APPMAG_r", )),
|
||||
(lambda x: cls[x] < 155, ("DIST", ))
|
||||
]
|
||||
|
||||
def __call__(self, fpath=None, apply_selection=True):
|
||||
if fpath is None:
|
||||
fpath = "/mnt/extraspace/rstiskalek/catalogs/5asfullmatch.fits"
|
||||
sel_steps = self.steps if apply_selection else None
|
||||
return read.SDSS(fpath, h=1, sel_steps=sel_steps)
|
||||
|
||||
|
||||
###############################################################################
|
||||
# Clusters #
|
||||
###############################################################################
|
||||
|
||||
clusters = {"Virgo": read.ObservedCluster(RA=hms_to_degrees(12, 27),
|
||||
dec=dms_to_degrees(12, 43),
|
||||
dist=16.5 * 0.7,
|
||||
name="Virgo"),
|
||||
}
|
||||
|
|
|
@ -15,12 +15,11 @@
|
|||
from warnings import warn
|
||||
|
||||
try:
|
||||
import MAS_library as MASL # noqa
|
||||
|
||||
from .density import (DensityField, PotentialField, # noqa
|
||||
TidalTensorField, VelocityField)
|
||||
from .interp import (evaluate_cartesian, evaluate_sky, field2rsp, # noqa
|
||||
fill_outside, make_sky, observer_vobs)
|
||||
from .utils import nside2radec, smoothen_field # noqa
|
||||
import MAS_library as MASL # noqa
|
||||
from .density import (DensityField, PotentialField, TidalTensorField, # noqa
|
||||
VelocityField, power_spectrum) # noqa
|
||||
from .interp import (evaluate_cartesian, evaluate_sky, field2rsp, # noqa
|
||||
fill_outside, make_sky, observer_vobs) # noqa
|
||||
from .utils import nside2radec, smoothen_field # noqa
|
||||
except ImportError:
|
||||
warn("MAS_library not found, `DensityField` will not be available", UserWarning) # noqa
|
||||
warn("MAS_library not found, `DensityField` and related Pylians-based routines will not be available") # noqa
|
||||
|
|
|
@ -18,6 +18,7 @@ Density field and cross-correlation calculations.
|
|||
from abc import ABC
|
||||
|
||||
import MAS_library as MASL
|
||||
import Pk_library as PKL
|
||||
import numpy
|
||||
from numba import jit
|
||||
from tqdm import trange
|
||||
|
@ -33,13 +34,7 @@ class BaseField(ABC):
|
|||
|
||||
@property
|
||||
def box(self):
|
||||
"""
|
||||
Simulation box information and transformations.
|
||||
|
||||
Returns
|
||||
-------
|
||||
:py:class:`csiborgtools.units.CSiBORGBox`
|
||||
"""
|
||||
"""Simulation box information and transformations."""
|
||||
return self._box
|
||||
|
||||
@box.setter
|
||||
|
@ -52,13 +47,7 @@ class BaseField(ABC):
|
|||
|
||||
@property
|
||||
def MAS(self):
|
||||
"""
|
||||
Mass-assignment scheme.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
"""
|
||||
"""Mass-assignment scheme."""
|
||||
if self._MAS is None:
|
||||
raise ValueError("`MAS` is not set.")
|
||||
return self._MAS
|
||||
|
@ -103,7 +92,6 @@ class DensityField(BaseField):
|
|||
Calculate the overdensity field from the density field.
|
||||
Defined as :math:`\rho/ <\rho> - 1`. Overwrites the input array.
|
||||
|
||||
|
||||
Parameters
|
||||
----------
|
||||
delta : 3-dimensional array of shape `(grid, grid, grid)`
|
||||
|
@ -117,7 +105,7 @@ class DensityField(BaseField):
|
|||
delta -= 1
|
||||
return delta
|
||||
|
||||
def __call__(self, parts, grid, flip_xz=True, nbatch=30, verbose=True):
|
||||
def __call__(self, pos, mass, grid, nbatch=30, verbose=True):
|
||||
"""
|
||||
Calculate the density field using a Pylians routine [1, 2].
|
||||
Iteratively loads the particles into memory, flips their `x` and `z`
|
||||
|
@ -126,13 +114,12 @@ class DensityField(BaseField):
|
|||
|
||||
Parameters
|
||||
----------
|
||||
parts : 2-dimensional array of shape `(n_parts, 7)`
|
||||
Particle positions, velocities and masses.
|
||||
Columns are: `x`, `y`, `z`, `vx`, `vy`, `vz`, `M`.
|
||||
pos : 2-dimensional array of shape `(n_parts, 3)`
|
||||
Particle positions
|
||||
mass : 1-dimensional array of shape `(n_parts,)`
|
||||
Particle masses
|
||||
grid : int
|
||||
Grid size.
|
||||
flip_xz : bool, optional
|
||||
Whether to flip the `x` and `z` coordinates.
|
||||
nbatch : int, optional
|
||||
Number of batches to split the particle loading into.
|
||||
verbose : bool, optional
|
||||
|
@ -150,24 +137,20 @@ class DensityField(BaseField):
|
|||
"""
|
||||
rho = numpy.zeros((grid, grid, grid), dtype=numpy.float32)
|
||||
|
||||
nparts = parts.shape[0]
|
||||
nparts = pos.shape[0]
|
||||
batch_size = nparts // nbatch
|
||||
start = 0
|
||||
|
||||
for __ in trange(nbatch + 1, disable=not verbose,
|
||||
desc="Loading particles for the density field"):
|
||||
end = min(start + batch_size, nparts)
|
||||
pos = parts[start:end]
|
||||
pos, vel, mass = pos[:, :3], pos[:, 3:6], pos[:, 6]
|
||||
batch_pos = pos[start:end]
|
||||
batch_mass = mass[start:end]
|
||||
|
||||
pos = force_single_precision(pos)
|
||||
vel = force_single_precision(vel)
|
||||
mass = force_single_precision(mass)
|
||||
if flip_xz:
|
||||
pos[:, [0, 2]] = pos[:, [2, 0]]
|
||||
vel[:, [0, 2]] = vel[:, [2, 0]]
|
||||
batch_pos = force_single_precision(batch_pos)
|
||||
batch_mass = force_single_precision(batch_mass)
|
||||
|
||||
MASL.MA(pos, rho, 1., self.MAS, W=mass, verbose=False)
|
||||
MASL.MA(batch_pos, rho, 1., self.MAS, W=batch_mass, verbose=False)
|
||||
if end == nparts:
|
||||
break
|
||||
start = end
|
||||
|
@ -178,8 +161,105 @@ class DensityField(BaseField):
|
|||
return rho
|
||||
|
||||
|
||||
# class SPHDensityVelocity(BaseField):
|
||||
# r"""
|
||||
# Density field calculation. Based primarily on routines of Pylians [1].
|
||||
#
|
||||
# Parameters
|
||||
# ----------
|
||||
# box : :py:class:`csiborgtools.read.CSiBORGBox`
|
||||
# The simulation box information and transformations.
|
||||
# MAS : str
|
||||
# Mass assignment scheme. Options are Options are: 'NGP' (nearest grid
|
||||
# point), 'CIC' (cloud-in-cell), 'TSC' (triangular-shape cloud), 'PCS'
|
||||
# (piecewise cubic spline).
|
||||
# paths : :py:class:`csiborgtools.read.Paths`
|
||||
# The simulation paths.
|
||||
#
|
||||
# References
|
||||
# ----------
|
||||
# [1] https://pylians3.readthedocs.io/
|
||||
# """
|
||||
#
|
||||
# def __init__(self, box, MAS):
|
||||
# self.box = box
|
||||
# self.MAS = MAS
|
||||
#
|
||||
# def overdensity_field(self, delta):
|
||||
# r"""
|
||||
# Calculate the overdensity field from the density field.
|
||||
# Defined as :math:`\rho/ <\rho> - 1`. Overwrites the input array.
|
||||
#
|
||||
# Parameters
|
||||
# ----------
|
||||
# delta : 3-dimensional array of shape `(grid, grid, grid)`
|
||||
# The density field.
|
||||
#
|
||||
# Returns
|
||||
# -------
|
||||
# 3-dimensional array of shape `(grid, grid, grid)`.
|
||||
# """
|
||||
# delta /= delta.mean()
|
||||
# delta -= 1
|
||||
# return delta
|
||||
#
|
||||
# def __call__(self, pos, mass, grid, nbatch=30, verbose=True):
|
||||
# """
|
||||
# Calculate the density field using a Pylians routine [1, 2].
|
||||
# Iteratively loads the particles into memory, flips their `x` and `z`
|
||||
# coordinates. Particles are assumed to be in box units, with positions
|
||||
# in [0, 1]
|
||||
#
|
||||
# Parameters
|
||||
# ----------
|
||||
# pos : 2-dimensional array of shape `(n_parts, 3)`
|
||||
# Particle positions
|
||||
# mass : 1-dimensional array of shape `(n_parts,)`
|
||||
# Particle masses
|
||||
# grid : int
|
||||
# Grid size.
|
||||
# nbatch : int, optional
|
||||
# Number of batches to split the particle loading into.
|
||||
# verbose : bool, optional
|
||||
# Verbosity flag.
|
||||
#
|
||||
# Returns
|
||||
# -------
|
||||
# 3-dimensional array of shape `(grid, grid, grid)`.
|
||||
#
|
||||
# References
|
||||
# ----------
|
||||
# [1] https://pylians3.readthedocs.io/
|
||||
# [2] https://github.com/franciscovillaescusa/Pylians3/blob/master
|
||||
# /library/MAS_library/MAS_library.pyx
|
||||
# """
|
||||
# rho = numpy.zeros((grid, grid, grid), dtype=numpy.float32)
|
||||
#
|
||||
# nparts = pos.shape[0]
|
||||
# batch_size = nparts // nbatch
|
||||
# start = 0
|
||||
#
|
||||
# for __ in trange(nbatch + 1, disable=not verbose,
|
||||
# desc="Loading particles for the density field"):
|
||||
# end = min(start + batch_size, nparts)
|
||||
# batch_pos = pos[start:end]
|
||||
# batch_mass = mass[start:end]
|
||||
#
|
||||
# batch_pos = force_single_precision(batch_pos)
|
||||
# batch_mass = force_single_precision(batch_mass)
|
||||
#
|
||||
# MASL.MA(batch_pos, rho, 1., self.MAS, W=batch_mass, verbose=False)
|
||||
# if end == nparts:
|
||||
# break
|
||||
# start = end
|
||||
#
|
||||
# # Divide by the cell volume in (kpc / h)^3
|
||||
# rho /= (self.box.boxsize / grid * 1e3)**3
|
||||
#
|
||||
# return rho
|
||||
|
||||
###############################################################################
|
||||
# Density field calculation #
|
||||
# Velocity field calculation #
|
||||
###############################################################################
|
||||
|
||||
|
||||
|
@ -242,7 +322,7 @@ class VelocityField(BaseField):
|
|||
/ numpy.sqrt(px**2 + py**2 + pz**2))
|
||||
return radvel
|
||||
|
||||
def __call__(self, parts, grid, flip_xz=True, nbatch=30,
|
||||
def __call__(self, pos, vel, mass, grid, flip_xz=True, nbatch=30,
|
||||
verbose=True):
|
||||
"""
|
||||
Calculate the velocity field using a Pylians routine [1, 2].
|
||||
|
@ -251,9 +331,12 @@ class VelocityField(BaseField):
|
|||
|
||||
Parameters
|
||||
----------
|
||||
parts : 2-dimensional array of shape `(n_parts, 7)`
|
||||
Particle positions, velocities and masses.
|
||||
Columns are: `x`, `y`, `z`, `vx`, `vy`, `vz`, `M`.
|
||||
pos : 2-dimensional array of shape `(n_parts, 3)`
|
||||
Particle positions.
|
||||
vel : 2-dimensional array of shape `(n_parts, 3)`
|
||||
Particle velocities.
|
||||
mass : 1-dimensional array of shape `(n_parts,)`
|
||||
Particle masses.
|
||||
grid : int
|
||||
Grid size.
|
||||
flip_xz : bool, optional
|
||||
|
@ -273,26 +356,26 @@ class VelocityField(BaseField):
|
|||
[2] https://github.com/franciscovillaescusa/Pylians3/blob/master
|
||||
/library/MAS_library/MAS_library.pyx
|
||||
"""
|
||||
rho_velx = numpy.zeros((grid, grid, grid), dtype=numpy.float32)
|
||||
rho_vely = numpy.zeros((grid, grid, grid), dtype=numpy.float32)
|
||||
rho_velz = numpy.zeros((grid, grid, grid), dtype=numpy.float32)
|
||||
rho_vel = [rho_velx, rho_vely, rho_velz]
|
||||
rho_vel = [numpy.zeros((grid, grid, grid), dtype=numpy.float32),
|
||||
numpy.zeros((grid, grid, grid), dtype=numpy.float32),
|
||||
numpy.zeros((grid, grid, grid), dtype=numpy.float32),
|
||||
]
|
||||
cellcounts = numpy.zeros((grid, grid, grid), dtype=numpy.float32)
|
||||
|
||||
nparts = parts.shape[0]
|
||||
nparts = pos.shape[0]
|
||||
batch_size = nparts // nbatch
|
||||
start = 0
|
||||
for __ in trange(nbatch + 1) if verbose else range(nbatch + 1):
|
||||
end = min(start + batch_size, nparts)
|
||||
pos = parts[start:end]
|
||||
pos, vel, mass = pos[:, :3], pos[:, 3:6], pos[:, 6]
|
||||
|
||||
pos = force_single_precision(pos)
|
||||
vel = force_single_precision(vel)
|
||||
mass = force_single_precision(mass)
|
||||
if flip_xz:
|
||||
pos[:, [0, 2]] = pos[:, [2, 0]]
|
||||
vel[:, [0, 2]] = vel[:, [2, 0]]
|
||||
batch_pos = pos[start:end]
|
||||
batch_vel = vel[start:end]
|
||||
batch_mass = mass[start:end]
|
||||
|
||||
batch_pos = force_single_precision(batch_pos)
|
||||
batch_vel = force_single_precision(batch_vel)
|
||||
batch_mass = force_single_precision(batch_mass)
|
||||
|
||||
vel *= mass.reshape(-1, 1)
|
||||
|
||||
for i in range(3):
|
||||
|
@ -308,7 +391,7 @@ class VelocityField(BaseField):
|
|||
for i in range(3):
|
||||
divide_nonzero(rho_vel[i], cellcounts)
|
||||
|
||||
return numpy.stack([rho_velx, rho_vely, rho_velz])
|
||||
return numpy.stack(rho_vel)
|
||||
|
||||
|
||||
###############################################################################
|
||||
|
@ -505,3 +588,35 @@ def eigenvalues_to_environment(eigvals, th):
|
|||
else:
|
||||
env[i, j, k] = 3
|
||||
return env
|
||||
|
||||
|
||||
###############################################################################
|
||||
# Power spectrum calculation #
|
||||
###############################################################################
|
||||
|
||||
|
||||
def power_spectrum(delta, boxsize, MAS, threads=1, verbose=True):
|
||||
"""
|
||||
Calculate the monopole power spectrum of the density field.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
delta : 3-dimensional array of shape `(grid, grid, grid)`
|
||||
The over-density field.
|
||||
boxsize : float
|
||||
The simulation box size in `Mpc / h`.
|
||||
MAS : str
|
||||
Mass assignment scheme used to calculate the density field.
|
||||
threads : int, optional
|
||||
Number of threads to use.
|
||||
verbose : bool, optional
|
||||
Verbosity flag.
|
||||
|
||||
Returns
|
||||
-------
|
||||
k, Pk : 1-dimensional arrays of shape `(grid,)`
|
||||
The wavenumbers and the power spectrum.
|
||||
"""
|
||||
axis = 2 # Axis along which compute the quadrupole and hexadecapole
|
||||
Pk = PKL.Pk(delta, boxsize, axis, MAS, threads, verbose)
|
||||
return Pk.k3D, Pk.Pk[:, 0]
|
||||
|
|
|
@ -98,9 +98,12 @@ def evaluate_sky(*fields, pos, mpc2box, smooth_scales=None, verbose=False):
|
|||
-------
|
||||
(list of) 1-dimensional array of shape `(n_samples, len(smooth_scales))`
|
||||
"""
|
||||
pos = force_single_precision(pos)
|
||||
# Make a copy of the positions to avoid modifying the input.
|
||||
pos = numpy.copy(pos)
|
||||
|
||||
pos = force_single_precision(pos)
|
||||
pos[:, 0] *= mpc2box
|
||||
|
||||
cart_pos = radec_to_cartesian(pos) + 0.5
|
||||
|
||||
if smooth_scales is not None:
|
||||
|
|
16
csiborgtools/halo/__init__.py
Normal file
16
csiborgtools/halo/__init__.py
Normal file
|
@ -0,0 +1,16 @@
|
|||
# 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.
|
||||
|
||||
from .prop import density_profile # noqa
|
46
csiborgtools/halo/prop.py
Normal file
46
csiborgtools/halo/prop.py
Normal file
|
@ -0,0 +1,46 @@
|
|||
# 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.
|
||||
|
||||
import numpy
|
||||
from scipy.stats import binned_statistic
|
||||
|
||||
from ..utils import periodic_distance
|
||||
|
||||
|
||||
def density_profile(pos, mass, center, nbins, boxsize):
|
||||
"""
|
||||
Calculate a density profile.
|
||||
"""
|
||||
raise NotImplementedError("Not implemented yet..")
|
||||
|
||||
rdist = periodic_distance(pos, center, boxsize)
|
||||
rmin, rmax = numpy.min(rdist), numpy.max(rdist)
|
||||
|
||||
bin_edges = numpy.logspace(numpy.log10(rmin), numpy.log10(rmax), nbins)
|
||||
|
||||
|
||||
rho, __, __ = binned_statistic(rdist, mass, statistic='sum',
|
||||
bins=bin_edges)
|
||||
|
||||
rho /= 4. / 3 * numpy.pi * (bin_edges[1:]**3 - bin_edges[:-1]**3)
|
||||
|
||||
print(bin_edges)
|
||||
|
||||
r = 0.5 * (bin_edges[1:] + bin_edges[:-1])
|
||||
|
||||
# r = numpy.sqrt(bin_edges[:1] * bin_edges[:-1])
|
||||
|
||||
return r, rho
|
||||
|
|
@ -12,7 +12,5 @@
|
|||
# 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.
|
||||
from .match import (ParticleOverlap, RealisationsMatcher, # noqa
|
||||
calculate_overlap, calculate_overlap_indxs, pos2cell, # noqa
|
||||
find_neighbour, get_halo_cell_limits, # noqa
|
||||
matching_max) # noqa
|
||||
from .match import (ParticleOverlap, RealisationsMatcher, calculate_overlap, # noqa
|
||||
pos2cell, find_neighbour, matching_max) # noqa
|
||||
|
|
|
@ -13,7 +13,8 @@
|
|||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
"""
|
||||
Support for matching halos between CSiBORG IC realisations.
|
||||
Support for matching halos between CSiBORG IC realisations based on their
|
||||
Lagrangian patch overlap.
|
||||
"""
|
||||
from abc import ABC
|
||||
from datetime import datetime
|
||||
|
@ -21,30 +22,19 @@ from functools import lru_cache
|
|||
from math import ceil
|
||||
|
||||
import numpy
|
||||
from scipy.ndimage import gaussian_filter
|
||||
|
||||
from numba import jit
|
||||
from scipy.ndimage import gaussian_filter
|
||||
from tqdm import tqdm, trange
|
||||
|
||||
from ..read import load_halo_particles
|
||||
|
||||
|
||||
class BaseMatcher(ABC):
|
||||
"""
|
||||
Base class for `RealisationsMatcher` and `ParticleOverlap`.
|
||||
"""
|
||||
"""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
|
||||
"""
|
||||
"""Number of cells in the box."""
|
||||
if self._box_size is None:
|
||||
raise RuntimeError("`box_size` has not been set.")
|
||||
return self._box_size
|
||||
|
@ -64,10 +54,6 @@ class BaseMatcher(ABC):
|
|||
grid distance from the center of the box to each side over which to
|
||||
evaluate the background density field. Must be less than or equal to
|
||||
half the box size.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bckg_halfsize : int
|
||||
"""
|
||||
if self._bckg_halfsize is None:
|
||||
raise RuntimeError("`bckg_halfsize` has not been set.")
|
||||
|
@ -130,10 +116,6 @@ class RealisationsMatcher(BaseMatcher):
|
|||
"""
|
||||
Multiplier of the sum of the initial Lagrangian patch sizes of a halo
|
||||
pair. Determines the range within which neighbors are returned.
|
||||
|
||||
Returns
|
||||
-------
|
||||
nmult : float
|
||||
"""
|
||||
return self._nmult
|
||||
|
||||
|
@ -148,10 +130,6 @@ class RealisationsMatcher(BaseMatcher):
|
|||
"""
|
||||
Tolerance on the absolute logarithmic mass difference of potential
|
||||
matches.
|
||||
|
||||
Returns
|
||||
-------
|
||||
float
|
||||
"""
|
||||
return self._dlogmass
|
||||
|
||||
|
@ -166,10 +144,6 @@ class RealisationsMatcher(BaseMatcher):
|
|||
"""
|
||||
Mass kind whose similarity is to be checked. Must be a valid key in the
|
||||
halo catalogue.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
"""
|
||||
return self._mass_kind
|
||||
|
||||
|
@ -181,17 +155,10 @@ class RealisationsMatcher(BaseMatcher):
|
|||
|
||||
@property
|
||||
def overlapper(self):
|
||||
"""
|
||||
The overlapper object.
|
||||
|
||||
Returns
|
||||
-------
|
||||
:py:class:`csiborgtools.match.ParticleOverlap`
|
||||
"""
|
||||
"""The overlapper object."""
|
||||
return self._overlapper
|
||||
|
||||
def cross(self, cat0, catx, particles0, particlesx, halo_map0, halo_mapx,
|
||||
delta_bckg, cache_size=10000, verbose=True):
|
||||
def cross(self, cat0, catx, delta_bckg, cache_size=10000, verbose=True):
|
||||
r"""
|
||||
Find all neighbours whose CM separation is less than `nmult` times the
|
||||
sum of their initial Lagrangian patch sizes and calculate their
|
||||
|
@ -204,16 +171,6 @@ class RealisationsMatcher(BaseMatcher):
|
|||
Halo catalogue of the reference simulation.
|
||||
catx : instance of :py:class:`csiborgtools.read.BaseCatalogue`
|
||||
Halo catalogue of the cross simulation.
|
||||
particles0 : 2-dimensional array
|
||||
Particles archive file of the reference simulation. The columns
|
||||
must be `x`, `y`, `z` and `M`.
|
||||
particlesx : 2-dimensional array
|
||||
Particles archive file of the cross simulation. The columns must be
|
||||
`x`, `y`, `z` and `M`.
|
||||
halo_map0 : 2-dimensional array
|
||||
Halo map of the reference simulation.
|
||||
halo_mapx : 2-dimensional array
|
||||
Halo map of the cross simulation.
|
||||
delta_bckg : 3-dimensional array
|
||||
Summed background density field of the reference and cross
|
||||
simulations calculated with particles assigned to halos at the
|
||||
|
@ -250,14 +207,11 @@ class RealisationsMatcher(BaseMatcher):
|
|||
aratio = numpy.abs(numpy.log10(catx[p][indx] / cat0[p][i]))
|
||||
match_indxs[i] = match_indxs[i][aratio < self.dlogmass]
|
||||
|
||||
hid2map0 = {hid: i for i, hid in enumerate(halo_map0[:, 0])}
|
||||
hid2mapx = {hid: i for i, hid in enumerate(halo_mapx[:, 0])}
|
||||
|
||||
# We will cache the halos from the cross simulation to speed up the I/O
|
||||
@lru_cache(maxsize=cache_size)
|
||||
def load_cached_halox(hid):
|
||||
return load_processed_halo(hid, particlesx, halo_mapx, hid2mapx,
|
||||
nshift=0, ncells=self.box_size)
|
||||
return load_processed_halo(hid, catx, nshift=0,
|
||||
ncells=self.box_size)
|
||||
|
||||
iterator = tqdm(
|
||||
cat0["index"],
|
||||
|
@ -273,8 +227,7 @@ class RealisationsMatcher(BaseMatcher):
|
|||
# Next, we find this halo's particles, total mass, minimum and
|
||||
# maximum cells and convert positions to cells.
|
||||
pos0, mass0, totmass0, mins0, maxs0 = load_processed_halo(
|
||||
k0, particles0, halo_map0, hid2map0, nshift=0,
|
||||
ncells=self.box_size)
|
||||
k0, cat0, nshift=0, ncells=self.box_size)
|
||||
|
||||
# We now loop over matches of this halo and calculate their
|
||||
# overlap, storing them in `_cross`.
|
||||
|
@ -298,9 +251,8 @@ class RealisationsMatcher(BaseMatcher):
|
|||
cross = numpy.asanyarray(cross, dtype=object)
|
||||
return match_indxs, cross
|
||||
|
||||
def smoothed_cross(self, cat0, catx, particles0, particlesx, halo_map0,
|
||||
halo_mapx, delta_bckg, match_indxs, smooth_kwargs,
|
||||
cache_size=10000, verbose=True):
|
||||
def smoothed_cross(self, cat0, catx, delta_bckg, match_indxs,
|
||||
smooth_kwargs, cache_size=10000, verbose=True):
|
||||
r"""
|
||||
Calculate the smoothed overlaps for pairs previously identified via
|
||||
`self.cross(...)` to have a non-zero NGP overlap.
|
||||
|
@ -311,16 +263,6 @@ class RealisationsMatcher(BaseMatcher):
|
|||
Halo catalogue of the reference simulation.
|
||||
catx : instance of :py:class:`csiborgtools.read.BaseCatalogue`
|
||||
Halo catalogue of the cross simulation.
|
||||
particles0 : 2-dimensional array
|
||||
Particles archive file of the reference simulation. The columns
|
||||
must be `x`, `y`, `z` and `M`.
|
||||
particlesx : 2-dimensional array
|
||||
Particles archive file of the cross simulation. The columns must be
|
||||
`x`, `y`, `z` and `M`.
|
||||
halo_map0 : 2-dimensional array
|
||||
Halo map of the reference simulation.
|
||||
halo_mapx : 2-dimensional array
|
||||
Halo map of the cross simulation.
|
||||
delta_bckg : 3-dimensional array
|
||||
Smoothed summed background density field of the reference and cross
|
||||
simulations calculated with particles assigned to halos at the
|
||||
|
@ -339,13 +281,11 @@ class RealisationsMatcher(BaseMatcher):
|
|||
overlaps : 1-dimensional array of arrays
|
||||
"""
|
||||
nshift = read_nshift(smooth_kwargs)
|
||||
hid2map0 = {hid: i for i, hid in enumerate(halo_map0[:, 0])}
|
||||
hid2mapx = {hid: i for i, hid in enumerate(halo_mapx[:, 0])}
|
||||
|
||||
@lru_cache(maxsize=cache_size)
|
||||
def load_cached_halox(hid):
|
||||
return load_processed_halo(hid, particlesx, halo_mapx, hid2mapx,
|
||||
nshift=nshift, ncells=self.box_size)
|
||||
return load_processed_halo(hid, catx, nshift=nshift,
|
||||
ncells=self.box_size)
|
||||
|
||||
iterator = tqdm(
|
||||
cat0["index"],
|
||||
|
@ -355,8 +295,7 @@ class RealisationsMatcher(BaseMatcher):
|
|||
cross = [numpy.asanyarray([], dtype=numpy.float32)] * match_indxs.size
|
||||
for i, k0 in enumerate(iterator):
|
||||
pos0, mass0, __, mins0, maxs0 = load_processed_halo(
|
||||
k0, particles0, halo_map0, hid2map0, nshift=nshift,
|
||||
ncells=self.box_size)
|
||||
k0, cat0, nshift=nshift, ncells=self.box_size)
|
||||
|
||||
# Now loop over the matches and calculate the smoothed overlap.
|
||||
_cross = numpy.full(match_indxs[i].size, numpy.nan, numpy.float32)
|
||||
|
@ -396,8 +335,7 @@ class ParticleOverlap(BaseMatcher):
|
|||
self.box_size = box_size
|
||||
self.bckg_halfsize = bckg_halfsize
|
||||
|
||||
def make_bckg_delta(self, particles, halo_map, hid2map, halo_cat,
|
||||
delta=None, verbose=False):
|
||||
def make_bckg_delta(self, cat, delta=None, verbose=False):
|
||||
"""
|
||||
Calculate a NGP density field of particles belonging to halos of a
|
||||
halo catalogue `halo_cat`. Particles are only counted within the
|
||||
|
@ -406,15 +344,8 @@ class ParticleOverlap(BaseMatcher):
|
|||
|
||||
Parameters
|
||||
----------
|
||||
particles : 2-dimensional array
|
||||
Particles archive file. The columns must be `x`, `y`, `z` and `M`.
|
||||
halo_map : 2-dimensional array
|
||||
Array containing start and end indices in the particle array
|
||||
corresponding to each halo.
|
||||
hid2map : dict
|
||||
Dictionary mapping halo IDs to `halo_map` array positions.
|
||||
halo_cat : instance of :py:class:`csiborgtools.read.BaseCatalogue`
|
||||
Halo catalogue.
|
||||
cat : instance of :py:class:`csiborgtools.read.BaseCatalogue`
|
||||
Halo catalogue of the reference simulation.
|
||||
delta : 3-dimensional array, optional
|
||||
Array to store the density field. If `None` a new array is
|
||||
created.
|
||||
|
@ -436,16 +367,17 @@ class ParticleOverlap(BaseMatcher):
|
|||
& (delta.dtype == numpy.float32))
|
||||
|
||||
iterator = tqdm(
|
||||
halo_cat["index"],
|
||||
cat["index"],
|
||||
desc=f"{datetime.now()} Calculating the background field",
|
||||
disable=not verbose
|
||||
)
|
||||
for hid in iterator:
|
||||
pos = load_halo_particles(hid, particles, halo_map, hid2map)
|
||||
pos = cat.halo_particles(hid, "pos", in_initial=True)
|
||||
if pos is None:
|
||||
continue
|
||||
|
||||
pos, mass = pos[:, :3], pos[:, 3]
|
||||
mass = cat.halo_particles(hid, "mass", in_initial=True)
|
||||
|
||||
pos = pos2cell(pos, self.box_size)
|
||||
|
||||
# We mask out particles outside the cubical high-resolution region
|
||||
|
@ -874,7 +806,7 @@ def calculate_overlap_indxs(delta1, delta2, cellmins, delta_bckg, nonzero,
|
|||
return intersect / (mass1 + mass2 - intersect)
|
||||
|
||||
|
||||
def load_processed_halo(hid, particles, halo_map, hid2map, ncells, nshift):
|
||||
def load_processed_halo(hid, cat, ncells, nshift):
|
||||
"""
|
||||
Load a processed halo from the `.h5` file. This is to be wrapped by a
|
||||
cacher.
|
||||
|
@ -883,14 +815,8 @@ def load_processed_halo(hid, particles, halo_map, hid2map, ncells, nshift):
|
|||
----------
|
||||
hid : int
|
||||
Halo ID.
|
||||
particles : 2-dimensional array
|
||||
Array of particles in box units. The columns must be `x`, `y`, `z`
|
||||
and `M`.
|
||||
halo_map : 2-dimensional array
|
||||
Array containing start and end indices in the particle array
|
||||
corresponding to each halo.
|
||||
hid2map : dict
|
||||
Dictionary mapping halo IDs to `halo_map` array positions.
|
||||
cat : instance of :py:class:`csiborgtools.read.BaseCatalogue`
|
||||
Halo catalogue.
|
||||
ncells : int
|
||||
Number of cells in the box density field.
|
||||
nshift : int
|
||||
|
@ -909,8 +835,8 @@ def load_processed_halo(hid, particles, halo_map, hid2map, ncells, nshift):
|
|||
maxs : len-3 tuple
|
||||
Maximum cell indices of the halo.
|
||||
"""
|
||||
pos = load_halo_particles(hid, particles, halo_map, hid2map)
|
||||
pos, mass = pos[:, :3], pos[:, 3]
|
||||
pos = cat.halo_particles(hid, "pos", in_initial=True)
|
||||
mass = cat.halo_particles(hid, "mass", in_initial=True)
|
||||
|
||||
pos = pos2cell(pos, ncells)
|
||||
mins, maxs = get_halo_cell_limits(pos, ncells=ncells, nshift=nshift)
|
||||
|
|
|
@ -12,9 +12,12 @@
|
|||
# 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.
|
||||
from .box_units import CSiBORGBox, QuijoteBox # noqa
|
||||
from .halo_cat import CSiBORGHaloCatalogue, QuijoteHaloCatalogue, fiducial_observers # noqa
|
||||
from .obs import SDSS, MCXCClusters, PlanckClusters, TwoMPPGalaxies, TwoMPPGroups # noqa
|
||||
from .paths import Paths # noqa
|
||||
from .readsim import MmainReader, CSiBORGReader, QuijoteReader, halfwidth_mask, load_halo_particles # noqa
|
||||
from .utils import cols_to_structured, read_h5 # noqa
|
||||
from .box_units import CSiBORGBox, QuijoteBox # noqa
|
||||
from .halo_cat import (CSiBORGCatalogue, QuijoteCatalogue, # noqa
|
||||
CSiBORGPHEWCatalogue, fiducial_observers) # noqa
|
||||
from .obs import (SDSS, MCXCClusters, PlanckClusters, TwoMPPGalaxies, # noqa
|
||||
TwoMPPGroups, ObservedCluster, match_array_to_no_masking) # noqa
|
||||
from .paths import Paths # noqa
|
||||
from .readsim import (CSiBORGReader, QuijoteReader, load_halo_particles, # noqa
|
||||
make_halomap_dict) # noqa
|
||||
from .utils import cols_to_structured, read_h5 # noqa
|
||||
|
|
|
@ -17,6 +17,7 @@ Simulation box unit transformations.
|
|||
"""
|
||||
from abc import ABC, abstractmethod, abstractproperty
|
||||
|
||||
import numpy
|
||||
from astropy import constants, units
|
||||
from astropy.cosmology import LambdaCDM
|
||||
|
||||
|
@ -28,80 +29,39 @@ from .readsim import CSiBORGReader, QuijoteReader
|
|||
|
||||
|
||||
class BaseBox(ABC):
|
||||
"""
|
||||
Base class for box units.
|
||||
"""
|
||||
_name = "box_units"
|
||||
_cosmo = None
|
||||
|
||||
@property
|
||||
def cosmo(self):
|
||||
"""
|
||||
The box cosmology.
|
||||
|
||||
Returns
|
||||
-------
|
||||
cosmo : `astropy.cosmology.LambdaCDM`
|
||||
"""
|
||||
if self._cosmo is None:
|
||||
raise ValueError("Cosmology not set.")
|
||||
return self._cosmo
|
||||
|
||||
@property
|
||||
def H0(self):
|
||||
r"""
|
||||
The Hubble parameter at the time of the snapshot in units of
|
||||
:math:`\mathrm{km} \mathrm{s}^{-1} \mathrm{Mpc}^{-1}`.
|
||||
|
||||
Returns
|
||||
-------
|
||||
H0 : float
|
||||
"""
|
||||
r"""Present Hubble parameter in :math:`\mathrm{km} \mathrm{s}^{-1}`"""
|
||||
return self.cosmo.H0.value
|
||||
|
||||
@property
|
||||
def rho_crit0(self):
|
||||
r"""
|
||||
Present-day critical density in :math:`M_\odot h^2 / \mathrm{cMpc}^3`.
|
||||
|
||||
Returns
|
||||
-------
|
||||
rho_crit0 : float
|
||||
"""
|
||||
"""Present-day critical density in M_sun h^2 / cMpc^3."""
|
||||
rho_crit0 = self.cosmo.critical_density0
|
||||
return rho_crit0.to_value(units.solMass / units.Mpc**3)
|
||||
|
||||
@property
|
||||
def h(self):
|
||||
r"""
|
||||
The little 'h' parameter at the time of the snapshot.
|
||||
|
||||
Returns
|
||||
-------
|
||||
h : float
|
||||
"""
|
||||
"""The little 'h' parameter at the time of the snapshot."""
|
||||
return self._h
|
||||
|
||||
@property
|
||||
def Om0(self):
|
||||
r"""
|
||||
The matter density parameter.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Om0 : float
|
||||
"""
|
||||
"""The present time matter density parameter."""
|
||||
return self.cosmo.Om0
|
||||
|
||||
@abstractproperty
|
||||
def boxsize(self):
|
||||
"""
|
||||
Box size in cMpc.
|
||||
|
||||
Returns
|
||||
-------
|
||||
boxsize : float
|
||||
"""
|
||||
"""Box size in cMpc."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
|
@ -116,8 +76,7 @@ class BaseBox(ABC):
|
|||
|
||||
Returns
|
||||
-------
|
||||
length : float
|
||||
Length in box units.
|
||||
float
|
||||
"""
|
||||
pass
|
||||
|
||||
|
@ -133,8 +92,7 @@ class BaseBox(ABC):
|
|||
|
||||
Returns
|
||||
-------
|
||||
length : float
|
||||
Length in :math:`\mathrm{cMpc} / h`
|
||||
float
|
||||
"""
|
||||
pass
|
||||
|
||||
|
@ -150,8 +108,7 @@ class BaseBox(ABC):
|
|||
|
||||
Returns
|
||||
-------
|
||||
mass : float
|
||||
Mass in box units.
|
||||
float
|
||||
"""
|
||||
pass
|
||||
|
||||
|
@ -167,8 +124,23 @@ class BaseBox(ABC):
|
|||
|
||||
Returns
|
||||
-------
|
||||
mass : float
|
||||
Mass in :math:`M_\odot / h`.
|
||||
float
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def m200c_to_r200c(self, m200c):
|
||||
"""
|
||||
Convert M200c to R200c in units of cMpc / h.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
m200c : float
|
||||
M200c in units of M_sun / h.
|
||||
|
||||
Returns
|
||||
-------
|
||||
float
|
||||
"""
|
||||
pass
|
||||
|
||||
|
@ -248,6 +220,12 @@ class CSiBORGBox(BaseBox):
|
|||
def boxsize(self):
|
||||
return self.box2mpc(1.)
|
||||
|
||||
def m200c_to_r200c(self, m200c):
|
||||
rho_crit = self.cosmo.critical_density(1 / self._aexp - 1)
|
||||
rho_crit = rho_crit.to_value(units.solMass / units.Mpc**3)
|
||||
r200c = (3 * m200c / (4 * numpy.pi * 200 * rho_crit))**(1 / 3)
|
||||
return r200c / self._aexp
|
||||
|
||||
|
||||
###############################################################################
|
||||
# Quijote fiducial cosmology box #
|
||||
|
@ -256,7 +234,7 @@ class CSiBORGBox(BaseBox):
|
|||
|
||||
class QuijoteBox(BaseBox):
|
||||
"""
|
||||
Quijote fiducial cosmology box.
|
||||
Quijote cosmology box.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
@ -289,33 +267,10 @@ class QuijoteBox(BaseBox):
|
|||
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 m200c_to_r200c(self, m200c):
|
||||
raise ValueError("Not implemented for Quijote boxes.")
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -383,6 +383,9 @@ class FitsSurvey(ABC):
|
|||
return out
|
||||
return out[self.selection_mask]
|
||||
|
||||
def __len__(self):
|
||||
return self.size
|
||||
|
||||
|
||||
###############################################################################
|
||||
# Planck clusters #
|
||||
|
@ -560,8 +563,7 @@ class SDSS(FitsSurvey):
|
|||
Parameters
|
||||
----------
|
||||
fpath : str, optional
|
||||
Path to the FITS file. By default
|
||||
`/mnt/extraspace/rstiskalek/catalogs/nsa_v1_0_1.fits`.
|
||||
Path to the FITS file.
|
||||
h : float, optional
|
||||
Little h. By default `h = 1`. The catalogue assumes this value.
|
||||
The routine properties should take care of little h conversion.
|
||||
|
@ -581,9 +583,7 @@ class SDSS(FitsSurvey):
|
|||
"""
|
||||
name = "SDSS"
|
||||
|
||||
def __init__(self, fpath=None, h=1, Om0=0.3175, sel_steps=None):
|
||||
if fpath is None:
|
||||
fpath = "/mnt/extraspace/rstiskalek/catalogs/nsa_v1_0_1.fits"
|
||||
def __init__(self, fpath, h=1, Om0=0.3175, sel_steps=None):
|
||||
self._file = fits.open(fpath, memmap=False)
|
||||
self.h = h
|
||||
|
||||
|
@ -719,3 +719,114 @@ class SDSS(FitsSurvey):
|
|||
Get `IN_DR7_LSS` and turn to a boolean array.
|
||||
"""
|
||||
return self.get_fitsitem("IN_DR7_LSS").astype(bool)
|
||||
|
||||
|
||||
###############################################################################
|
||||
# Individual observations #
|
||||
###############################################################################
|
||||
|
||||
|
||||
class BaseSingleObservation(ABC):
|
||||
"""
|
||||
Base class to hold information about a single object.
|
||||
"""
|
||||
def __init__(self):
|
||||
self._spherical_pos = None
|
||||
self._name = None
|
||||
|
||||
@property
|
||||
def spherical_pos(self):
|
||||
"""
|
||||
Spherical position of the observation in dist/RA/dec in Mpc / h and
|
||||
degrees, respectively.
|
||||
|
||||
Returns
|
||||
-------
|
||||
1-dimensional array of shape (3,)
|
||||
"""
|
||||
if self._spherical_pos is None:
|
||||
raise ValueError("`spherical_pos` is not set!")
|
||||
return self._spherical_pos
|
||||
|
||||
@spherical_pos.setter
|
||||
def spherical_pos(self, pos):
|
||||
if isinstance(pos, (list, tuple)):
|
||||
pos = numpy.array(pos)
|
||||
|
||||
if not pos.shape == (3,):
|
||||
raise ValueError("`spherical_pos` must be a of shape (3,).")
|
||||
|
||||
self._spherical_pos = pos
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""
|
||||
Observated object name.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
"""
|
||||
if self._name is None:
|
||||
raise ValueError("`name` is not set!")
|
||||
return self._name
|
||||
|
||||
@name.setter
|
||||
def name(self, name):
|
||||
if not isinstance(name, str):
|
||||
raise ValueError("`name` must be a string.")
|
||||
self._name = name
|
||||
|
||||
|
||||
class ObservedCluster(BaseSingleObservation):
|
||||
"""
|
||||
Class to hold information about an observed cluster.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
RA : float
|
||||
Right ascension in degrees.
|
||||
dec : float
|
||||
Declination in degrees.
|
||||
dist : float
|
||||
Distance in Mpc / h.
|
||||
name : str
|
||||
Cluster name.
|
||||
"""
|
||||
def __init__(self, RA, dec, dist, name):
|
||||
super().__init__()
|
||||
self.name = name
|
||||
self.spherical_pos = [dist, RA, dec]
|
||||
|
||||
|
||||
###############################################################################
|
||||
# Utility functions #
|
||||
###############################################################################
|
||||
|
||||
def match_array_to_no_masking(arr, surv):
|
||||
"""
|
||||
Match an array to a survey without masking.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
arr : n-dimensional array
|
||||
Array to match.
|
||||
surv : survey class
|
||||
Survey class.
|
||||
|
||||
Returns
|
||||
-------
|
||||
out : n-dimensional array
|
||||
"""
|
||||
dtype = arr.dtype
|
||||
if arr.ndim > 1:
|
||||
shape = arr.shape
|
||||
out = numpy.full((surv.selection_mask.size, *shape[1:]), numpy.nan,
|
||||
dtype=dtype)
|
||||
else:
|
||||
out = numpy.full(surv.selection_mask.size, numpy.nan, dtype=dtype)
|
||||
|
||||
for i, indx in enumerate(surv["INDEX"]):
|
||||
out[indx] = arr[i]
|
||||
|
||||
return out
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
"""CSiBORG paths manager."""
|
||||
from glob import glob
|
||||
from glob import glob, iglob
|
||||
from os import makedirs
|
||||
from os.path import isdir, join
|
||||
from warnings import warn
|
||||
|
@ -61,13 +61,7 @@ class Paths:
|
|||
|
||||
@property
|
||||
def srcdir(self):
|
||||
"""
|
||||
Path to the folder where CSiBORG simulations are stored.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
"""
|
||||
"""Path to the folder where CSiBORG simulations are stored."""
|
||||
if self._srcdir is None:
|
||||
raise ValueError("`srcdir` is not set!")
|
||||
return self._srcdir
|
||||
|
@ -81,13 +75,7 @@ class Paths:
|
|||
|
||||
@property
|
||||
def borg_dir(self):
|
||||
"""
|
||||
Path to the folder where BORG MCMC chains are stored.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
"""
|
||||
"""Path to the folder where BORG MCMC chains are stored."""
|
||||
if self._borg_dir is None:
|
||||
raise ValueError("`borg_dir` is not set!")
|
||||
return self._borg_dir
|
||||
|
@ -101,13 +89,7 @@ class Paths:
|
|||
|
||||
@property
|
||||
def quijote_dir(self):
|
||||
"""
|
||||
Path to the folder where Quijote simulations are stored.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
"""
|
||||
"""Path to the folder where Quijote simulations are stored."""
|
||||
if self._quijote_dir is None:
|
||||
raise ValueError("`quijote_dir` is not set!")
|
||||
return self._quijote_dir
|
||||
|
@ -121,13 +103,7 @@ class Paths:
|
|||
|
||||
@property
|
||||
def postdir(self):
|
||||
"""
|
||||
Path to the folder where post-processed files are stored.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
"""
|
||||
"""Path to the folder where post-processed files are stored."""
|
||||
if self._postdir is None:
|
||||
raise ValueError("`postdir` is not set!")
|
||||
return self._postdir
|
||||
|
@ -139,19 +115,6 @@ class Paths:
|
|||
check_directory(path)
|
||||
self._postdir = path
|
||||
|
||||
@property
|
||||
def temp_dumpdir(self):
|
||||
"""
|
||||
Path to a temporary dumping folder.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
"""
|
||||
fpath = join(self.postdir, "temp")
|
||||
try_create_directory(fpath)
|
||||
return fpath
|
||||
|
||||
@staticmethod
|
||||
def quijote_fiducial_nsim(nsim, nobs=None):
|
||||
"""
|
||||
|
@ -167,7 +130,7 @@ class Paths:
|
|||
|
||||
Returns
|
||||
-------
|
||||
id : str
|
||||
str
|
||||
"""
|
||||
if nobs is None:
|
||||
assert isinstance(nsim, str)
|
||||
|
@ -190,36 +153,14 @@ class Paths:
|
|||
"""
|
||||
return join(self.borg_dir, "mcmc", f"mcmc_{nsim}.h5")
|
||||
|
||||
def fof_membership(self, nsim, simname, sorted=False):
|
||||
"""
|
||||
Path to the file containing the FoF particle membership.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
nsim : int
|
||||
IC realisation index.
|
||||
simname : str
|
||||
Simulation name. Must be one of `csiborg` or `quijote`.
|
||||
sorted : bool, optional
|
||||
Whether to return path to the file that is sorted in the same
|
||||
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", )
|
||||
try_create_directory(fdir)
|
||||
fout = join(fdir, f"fof_membership_{nsim}.npy")
|
||||
if sorted:
|
||||
fout = fout.replace(".npy", "_sorted.npy")
|
||||
return fout
|
||||
|
||||
def fof_cat(self, nsim, simname, from_quijote_backup=False):
|
||||
def fof_cat(self, nsnap, nsim, simname, from_quijote_backup=False):
|
||||
r"""
|
||||
Path to the :math:`z = 0` FoF halo catalogue.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
nsnap : int
|
||||
Snapshot index.
|
||||
nsim : int
|
||||
IC realisation index.
|
||||
simname : str
|
||||
|
@ -228,15 +169,15 @@ class Paths:
|
|||
Whether to return the path to the Quijote FoF catalogue from the
|
||||
backup.
|
||||
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
"""
|
||||
if simname == "csiborg":
|
||||
fdir = join(self.postdir, "FoF_membership", )
|
||||
fdir = join(self.postdir, "halo_maker", f"ramses_{nsim}",
|
||||
f"output_{str(nsnap).zfill(5)}", "FOF")
|
||||
try_create_directory(fdir)
|
||||
return join(fdir, f"halo_catalog_{nsim}_FOF.txt")
|
||||
return join(fdir, "fort.132")
|
||||
elif simname == "quijote":
|
||||
if from_quijote_backup:
|
||||
return join(self.quijote_dir, "halos_backup", str(nsim))
|
||||
|
@ -245,57 +186,6 @@ class Paths:
|
|||
else:
|
||||
raise ValueError(f"Unknown simulation name `{simname}`.")
|
||||
|
||||
def mmain(self, nsnap, nsim):
|
||||
"""
|
||||
Path to the `mmain` CSiBORG files of summed substructure.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
nsnap : int
|
||||
Snapshot index.
|
||||
nsim : int
|
||||
IC realisation index.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
"""
|
||||
fdir = join(self.postdir, "mmain")
|
||||
try_create_directory(fdir)
|
||||
return join(
|
||||
fdir, f"mmain_{str(nsim).zfill(5)}_{str(nsnap).zfill(5)}.npz")
|
||||
|
||||
def initmatch(self, nsim, simname, kind):
|
||||
"""
|
||||
Path to the `initmatch` files where the halo match between the
|
||||
initial and final snapshot of a CSiBORG realisaiton is stored.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
nsim : int
|
||||
IC realisation index.
|
||||
simname : str
|
||||
Simulation name. Must be one of `csiborg` or `quijote`.
|
||||
kind : str
|
||||
Type of match. Must be one of `particles` or `fit`.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
"""
|
||||
assert kind in ["particles", "fit"]
|
||||
ftype = "npy" if kind == "fit" else "h5"
|
||||
|
||||
if simname == "csiborg":
|
||||
fdir = join(self.postdir, "initmatch")
|
||||
elif simname == "quijote":
|
||||
fdir = join(self.quijote_dir, "initmatch")
|
||||
else:
|
||||
raise ValueError(f"Unknown simulation name `{simname}`.")
|
||||
|
||||
try_create_directory(fdir)
|
||||
return join(fdir, f"{kind}_{str(nsim).zfill(5)}.{ftype}")
|
||||
|
||||
def get_ics(self, simname, from_quijote_backup=False):
|
||||
"""
|
||||
Get available IC realisation IDs for either the CSiBORG or Quijote
|
||||
|
@ -411,7 +301,7 @@ class Paths:
|
|||
|
||||
Returns
|
||||
-------
|
||||
snapstr
|
||||
str
|
||||
"""
|
||||
simpath = self.snapshots(nsim, simname, tonew=nsnap == 1)
|
||||
if simname == "csiborg":
|
||||
|
@ -422,7 +312,27 @@ class Paths:
|
|||
nsnap = str(nsnap).zfill(3)
|
||||
return join(simpath, f"snapdir_{nsnap}", f"snap_{nsnap}")
|
||||
|
||||
def particles(self, nsim, simname):
|
||||
def merger_tree_file(self, nsnap, nsim):
|
||||
"""
|
||||
Path to the CSiBORG on-the-fly generated merger tree file.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
nsnap : int
|
||||
Snapshot index.
|
||||
nsim : int
|
||||
IC realisation index.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
"""
|
||||
nsim = str(nsim)
|
||||
nsnap = str(nsnap).zfill(5)
|
||||
return join(self.srcdir, f"ramses_out_{nsim}",
|
||||
f"output_{nsnap}", f"mergertree_{nsnap}.dat")
|
||||
|
||||
def processed_output(self, nsim, simname, halo_finder):
|
||||
"""
|
||||
Path to the files containing all particles of a CSiBORG realisation at
|
||||
:math:`z = 0`.
|
||||
|
@ -433,22 +343,80 @@ class Paths:
|
|||
IC realisation index.
|
||||
simname : str
|
||||
Simulation name. Must be one of `csiborg` or `quijote`.
|
||||
halo_finder : str
|
||||
Halo finder name.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
"""
|
||||
if simname == "csiborg":
|
||||
fdir = join(self.postdir, "particles")
|
||||
fdir = join(self.postdir, "processed_output")
|
||||
elif simname == "quijote":
|
||||
fdir = join(self.quijote_dir, "Particles_fiducial")
|
||||
else:
|
||||
raise ValueError(f"Unknown simulation name `{simname}`.")
|
||||
|
||||
try_create_directory(fdir)
|
||||
fname = f"parts_{str(nsim).zfill(5)}.h5"
|
||||
fname = f"parts_{halo_finder}_{str(nsim).zfill(5)}.hdf5"
|
||||
return join(fdir, fname)
|
||||
|
||||
def processed_phew(self, nsim):
|
||||
"""
|
||||
Path to the files containing PHEW CSiBORG catalogues.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
nsim : int
|
||||
IC realisation index.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
"""
|
||||
fdir = join(self.postdir, "processed_output")
|
||||
try_create_directory(fdir)
|
||||
return join(fdir, f"phew_{str(nsim).zfill(5)}.hdf5")
|
||||
|
||||
def processed_merger_tree(self, nsim):
|
||||
"""
|
||||
Path to the files containing the processed original merger tree files.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
nsim : int
|
||||
IC realisation index.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
"""
|
||||
fdir = join(self.postdir, "processed_output")
|
||||
try_create_directory(fdir)
|
||||
return join(fdir, f"merger_{str(nsim).zfill(5)}.hdf5")
|
||||
|
||||
def halomaker_particle_membership(self, nsnap, nsim, halo_finder):
|
||||
"""
|
||||
Path to the HaloMaker particle membership file (CSiBORG only).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
nsnap : int
|
||||
Snapshot index.
|
||||
nsim : int
|
||||
IC realisation index.
|
||||
halo_finder : str
|
||||
Halo finder name.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
"""
|
||||
fdir = join(self.postdir, "halo_maker", f"ramses_{nsim}",
|
||||
f"output_{str(nsnap).zfill(5)}", halo_finder)
|
||||
fpath = join(fdir, "*particle_membership*")
|
||||
return next(iglob(fpath, recursive=True), None)
|
||||
|
||||
def ascii_positions(self, nsim, kind):
|
||||
"""
|
||||
Path to ASCII files containing the positions of particles or halos.
|
||||
|
@ -469,35 +437,6 @@ class Paths:
|
|||
|
||||
return join(fdir, fname)
|
||||
|
||||
def structfit(self, nsnap, nsim, simname):
|
||||
"""
|
||||
Path to the halo catalogue from `fit_halos.py`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
nsnap : int
|
||||
Snapshot index.
|
||||
nsim : int
|
||||
IC realisation index.
|
||||
simname : str
|
||||
Simulation name. Must be one of `csiborg` or `quijote`.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
"""
|
||||
if simname == "csiborg":
|
||||
fdir = join(self.postdir, "structfit")
|
||||
elif simname == "quijote":
|
||||
fdir = join(self.quijote_dir, "structfit")
|
||||
else:
|
||||
raise ValueError(f"Unknown simulation name `{simname}`.")
|
||||
|
||||
try_create_directory(fdir)
|
||||
|
||||
fname = f"out_{str(nsim).zfill(5)}_{str(nsnap).zfill(5)}.npy"
|
||||
return join(fdir, fname)
|
||||
|
||||
def overlap(self, simname, nsim0, nsimx, min_logmass, smoothed):
|
||||
"""
|
||||
Path to the overlap files between two CSiBORG simulations.
|
||||
|
@ -688,31 +627,6 @@ class Paths:
|
|||
fname = f"obs_vp_{MAS}_{str(nsim).zfill(5)}_{grid}.npz"
|
||||
return join(fdir, fname)
|
||||
|
||||
def halo_counts(self, simname, nsim, from_quijote_backup=False):
|
||||
"""
|
||||
Path to the files containing the binned halo counts.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
simname : str
|
||||
Simulation name. Must be `csiborg`, `quijote` or `quijote_full`.
|
||||
nsim : int
|
||||
IC realisation index.
|
||||
from_quijote_backup : bool, optional
|
||||
Whether to return the path to the Quijote halo counts from the
|
||||
backup catalogues.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
"""
|
||||
fdir = join(self.postdir, "HMF")
|
||||
try_create_directory(fdir)
|
||||
fname = f"halo_counts_{simname}_{str(nsim).zfill(5)}.npz"
|
||||
if from_quijote_backup:
|
||||
fname = fname.replace("halo_counts", "halo_counts_backup")
|
||||
return join(fdir, fname)
|
||||
|
||||
def cross_nearest(self, simname, run, kind, nsim=None, nobs=None):
|
||||
"""
|
||||
Path to the files containing distance from a halo in a reference
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -15,6 +15,7 @@
|
|||
"""Collection of stand-off utility functions used in the scripts."""
|
||||
import numpy
|
||||
from numba import jit
|
||||
from datetime import datetime
|
||||
|
||||
###############################################################################
|
||||
# Positions #
|
||||
|
@ -87,7 +88,7 @@ def periodic_distance_two_points(p1, p2, boxsize):
|
|||
return dist**0.5
|
||||
|
||||
|
||||
@jit(nopython=True)
|
||||
@jit(nopython=True, boundscheck=False)
|
||||
def periodic_wrap_grid(pos, boxsize=1):
|
||||
"""Wrap positions in a periodic box."""
|
||||
for n in range(pos.shape[0]):
|
||||
|
@ -139,17 +140,34 @@ def radec_to_cartesian(X):
|
|||
"""
|
||||
dist, ra, dec = X[:, 0], X[:, 1], X[:, 2]
|
||||
|
||||
ra *= numpy.pi / 180
|
||||
dec *= numpy.pi / 180
|
||||
cdec = numpy.cos(dec)
|
||||
|
||||
cdec = numpy.cos(dec * numpy.pi / 180)
|
||||
return numpy.vstack([
|
||||
dist * cdec * numpy.cos(ra),
|
||||
dist * cdec * numpy.sin(ra),
|
||||
dist * numpy.sin(dec)
|
||||
dist * cdec * numpy.cos(ra * numpy.pi / 180),
|
||||
dist * cdec * numpy.sin(ra * numpy.pi / 180),
|
||||
dist * numpy.sin(dec * numpy.pi / 180)
|
||||
]).T
|
||||
|
||||
|
||||
@jit(nopython=True, fastmath=True, boundscheck=False)
|
||||
def great_circle_distance(x1, x2):
|
||||
"""
|
||||
Great circle distance between two points on a sphere, defined by RA and
|
||||
dec, both in degrees.
|
||||
"""
|
||||
ra1, dec1 = x1
|
||||
ra2, dec2 = x2
|
||||
|
||||
ra1 *= numpy.pi / 180
|
||||
dec1 *= numpy.pi / 180
|
||||
ra2 *= numpy.pi / 180
|
||||
dec2 *= numpy.pi / 180
|
||||
|
||||
return 180 / numpy.pi * numpy.arccos(
|
||||
numpy.sin(dec1) * numpy.sin(dec2)
|
||||
+ numpy.cos(dec1) * numpy.cos(dec2) * numpy.cos(ra1 - ra2)
|
||||
)
|
||||
|
||||
|
||||
def cosine_similarity(x, y):
|
||||
r"""
|
||||
Calculate the cosine similarity between two Cartesian vectors. Defined
|
||||
|
@ -179,6 +197,36 @@ def cosine_similarity(x, y):
|
|||
return out[0] if out.size == 1 else out
|
||||
|
||||
|
||||
def hms_to_degrees(hours, minutes=None, seconds=None):
|
||||
"""
|
||||
Convert hours, minutes and seconds to degrees.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
hours, minutes, seconds : float
|
||||
|
||||
Returns
|
||||
-------
|
||||
float
|
||||
"""
|
||||
return hours * 15 + (minutes or 0) / 60 * 15 + (seconds or 0) / 3600 * 15
|
||||
|
||||
|
||||
def dms_to_degrees(degrees, arcminutes=None, arcseconds=None):
|
||||
"""
|
||||
Convert degrees, arcminutes and arcseconds to decimal degrees.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
degrees, arcminutes, arcseconds : float
|
||||
|
||||
Returns
|
||||
-------
|
||||
float
|
||||
"""
|
||||
return degrees + (arcminutes or 0) / 60 + (arcseconds or 0) / 3600
|
||||
|
||||
|
||||
def real2redshift(pos, vel, observer_location, observer_velocity, box,
|
||||
periodic_wrap=True, make_copy=True):
|
||||
r"""
|
||||
|
@ -262,3 +310,9 @@ def binned_statistic(x, y, left_edges, bin_width, statistic):
|
|||
if numpy.any(mask):
|
||||
out[i] = statistic(y[mask])
|
||||
return out
|
||||
|
||||
|
||||
def fprint(msg, verbose=True):
|
||||
"""Print and flush a message with a timestamp."""
|
||||
if verbose:
|
||||
print(f"{datetime.now()}: {msg}", flush=True)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue