mirror of
https://github.com/Richard-Sti/csiborgtools.git
synced 2024-12-22 18:18:03 +00:00
Mass enclosed in a sphere calculation (#5)
* rename nb * add merge func * rm bug.. * add static methods * save rmin, rmax * add properties for box units * move radial info to the clump * add Npart explicit * move where rmin, rmax obtained * add box cosmo * add __getattr__ * add comments in units * add enclosed spherical mass * rm unused variables * add clump mass setter * add the halo index * add enclosed overdensity * add enclosed_overdensity * simplify loss func * opt result to nan * change Rs to log10 * change back to attribs * add H0 and h props * add setattrs * Remove global constants * move Msuncgs above * add crit density * add dots * add r200c and r500c * add M200 and M500 * make lowercase * output r200, r500, m200, m500 * update TODO * update README * fit NFW only up to r200 * add r178, m178 * add m178, r178 * update TODO
This commit is contained in:
parent
88e2132232
commit
ba98ecc299
9 changed files with 540 additions and 188 deletions
19
README.md
19
README.md
|
@ -1,11 +1,16 @@
|
||||||
# CSiBORG analysis tools :dart:
|
# CSiBORG tools
|
||||||
|
|
||||||
## TODO :scroll:
|
## :scroll: Short-term TODO
|
||||||
- [ ] Calculate $M_{\rm vir}, R_{\rm vir}, c$ from $R_s, \rho_0, \ldots$
|
- [x] Calculate $M_{\rm vir}, R_{\rm vir}, c$ from $R_s, \rho_0, \ldots$
|
||||||
- [ ] Calculate $M_{\rm 500c}$ by sphere shrinking
|
- [x] In `NFWPosterior` correct for the radius in which particles are fitted.
|
||||||
- [ ] Calculate the cross-correlation in CSiBORG. Should see the scale of the constraints?
|
- [x] Calculate $M_{\rm 500c}$ by sphere shrinking
|
||||||
|
- [x] Change to log10 of the scale factor
|
||||||
|
- [ ] Calculate uncertainty on $R_{\rm s}$, switch to `JAX` and get gradients.
|
||||||
|
|
||||||
|
|
||||||
|
## :hourglass: Long-term TODO
|
||||||
- [ ] Improve file naming system
|
- [ ] Improve file naming system
|
||||||
|
- [ ] Calculate the cross-correlation in CSiBORG. Should see the scale of the constraints?
|
||||||
|
|
||||||
|
|
||||||
## Open questions :bulb:
|
## :bulb: Open questions
|
||||||
- Get uncertainty on the fitted $R_{\rm s}$? If so get this directly from JAX.
|
|
|
@ -18,6 +18,7 @@ Tools for splitting the particles and a clump object.
|
||||||
|
|
||||||
|
|
||||||
import numpy
|
import numpy
|
||||||
|
from scipy.optimize import minimize_scalar
|
||||||
from os import remove
|
from os import remove
|
||||||
from warnings import warn
|
from warnings import warn
|
||||||
from os.path import join
|
from os.path import join
|
||||||
|
@ -235,7 +236,7 @@ def pick_single_clump(n, particles, particle_clumps, clumps):
|
||||||
|
|
||||||
|
|
||||||
class Clump:
|
class Clump:
|
||||||
"""
|
r"""
|
||||||
A clump (halo) object to handle the particles and their clump's data.
|
A clump (halo) object to handle the particles and their clump's data.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
|
@ -254,28 +255,40 @@ class Clump:
|
||||||
Clump center coordinate along the y-axis.
|
Clump center coordinate along the y-axis.
|
||||||
z0 : float
|
z0 : float
|
||||||
Clump center coordinate along the z-axis.
|
Clump center coordinate along the z-axis.
|
||||||
clump_mass : float
|
clump_mass : float, optional
|
||||||
Mass of the clump.
|
Mass of the clump. By default not set.
|
||||||
vx : 1-dimensional array
|
vx : 1-dimensional array, optional
|
||||||
Particle velocity along the x-axis.
|
Particle velocity along the x-axis. By default not set.
|
||||||
vy : 1-dimensional array
|
vy : 1-dimensional array, optional
|
||||||
Particle velocity along the y-axis.
|
Particle velocity along the y-axis. By default not set.
|
||||||
vz : 1-dimensional array
|
vz : 1-dimensional array, optional
|
||||||
Particle velocity along the z-axis.
|
Particle velocity along the z-axis. By default not set.
|
||||||
|
index : int, optional
|
||||||
|
The halo finder index of this clump. By default not set.
|
||||||
|
rhoc : float, optional
|
||||||
|
The critical density :math:`\rho_c` at this snapshot in box units. By
|
||||||
|
default not set.
|
||||||
"""
|
"""
|
||||||
_r = None
|
_r = None
|
||||||
|
_rmin = None
|
||||||
|
_rmax = None
|
||||||
_pos = None
|
_pos = None
|
||||||
_clump_pos = None
|
_clump_pos = None
|
||||||
_clump_mass = None
|
_clump_mass = None
|
||||||
_vel = None
|
_vel = None
|
||||||
|
_Npart = None
|
||||||
|
_index = None
|
||||||
|
_rhoc = None
|
||||||
|
|
||||||
def __init__(self, x, y, z, m, x0, y0, z0, clump_mass=None,
|
def __init__(self, x, y, z, m, x0, y0, z0, clump_mass=None,
|
||||||
vx=None, vy=None, vz=None):
|
vx=None, vy=None, vz=None, index=None, rhoc=None):
|
||||||
self.pos = (x, y, z, x0, y0, z0)
|
self.pos = (x, y, z, x0, y0, z0)
|
||||||
self.clump_pos = (x0, y0, z0)
|
self.clump_pos = (x0, y0, z0)
|
||||||
self.clump_mass = clump_mass
|
self.clump_mass = clump_mass
|
||||||
self.vel = (vx, vy, vz)
|
self.vel = (vx, vy, vz)
|
||||||
self.m = m
|
self.m = m
|
||||||
|
self.index = index
|
||||||
|
self.rhoc = rhoc
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def pos(self):
|
def pos(self):
|
||||||
|
@ -294,7 +307,46 @@ class Clump:
|
||||||
"""Sets `pos` and calculates radial distance."""
|
"""Sets `pos` and calculates radial distance."""
|
||||||
x, y, z, x0, y0, z0 = X
|
x, y, z, x0, y0, z0 = X
|
||||||
self._pos = numpy.vstack([x - x0, y - y0, z - z0]).T
|
self._pos = numpy.vstack([x - x0, y - y0, z - z0]).T
|
||||||
self.r = numpy.sum(self.pos**2, axis=1)**0.5
|
self._r = numpy.sum(self.pos**2, axis=1)**0.5
|
||||||
|
self._rmin = numpy.min(self._r)
|
||||||
|
self._rmax = numpy.max(self._r)
|
||||||
|
self._Npart = self._r.size
|
||||||
|
|
||||||
|
@property
|
||||||
|
def r(self):
|
||||||
|
"""
|
||||||
|
Radial distance of the particles from the clump peak.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
r : 1-dimensional array
|
||||||
|
Array of shape `(n_particles, )`.
|
||||||
|
"""
|
||||||
|
return self._r
|
||||||
|
|
||||||
|
@property
|
||||||
|
def rmin(self):
|
||||||
|
"""
|
||||||
|
The minimum radial distance of a particle.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
rmin : float
|
||||||
|
The minimum distance.
|
||||||
|
"""
|
||||||
|
return self._rmin
|
||||||
|
|
||||||
|
@property
|
||||||
|
def rmax(self):
|
||||||
|
"""
|
||||||
|
The maximum radial distance of a particle.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
rmin : float
|
||||||
|
The maximum distance.
|
||||||
|
"""
|
||||||
|
return self._rmax
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def Npart(self):
|
def Npart(self):
|
||||||
|
@ -306,7 +358,7 @@ class Clump:
|
||||||
Npart : int
|
Npart : int
|
||||||
Number of particles.
|
Number of particles.
|
||||||
"""
|
"""
|
||||||
return self.r.size
|
return self._Npart
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def clump_pos(self):
|
def clump_pos(self):
|
||||||
|
@ -338,12 +390,14 @@ class Clump:
|
||||||
mass : float
|
mass : float
|
||||||
Clump mass.
|
Clump mass.
|
||||||
"""
|
"""
|
||||||
|
if self._clump_mass is None:
|
||||||
|
raise ValueError("Clump mass `clump_mass` has not been set.")
|
||||||
return self._clump_mass
|
return self._clump_mass
|
||||||
|
|
||||||
@clump_mass.setter
|
@clump_mass.setter
|
||||||
def clump_mass(self, mass):
|
def clump_mass(self, mass):
|
||||||
"""Sets `clump_mass`, making sure it is a float."""
|
"""Sets `clump_mass`, making sure it is a float."""
|
||||||
if not isinstance(mass, float):
|
if mass is not None and not isinstance(mass, float):
|
||||||
raise ValueError("`clump_mass` must be a float.")
|
raise ValueError("`clump_mass` must be a float.")
|
||||||
self._clump_mass = mass
|
self._clump_mass = mass
|
||||||
|
|
||||||
|
@ -391,25 +445,46 @@ class Clump:
|
||||||
self._m = m
|
self._m = m
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def r(self):
|
def index(self):
|
||||||
"""
|
"""
|
||||||
Radial distance of particles from the clump peak.
|
The halo finder clump index.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
r : 1-dimensional array
|
hindex : int
|
||||||
Array of shape `(n_particles, )`
|
The index.
|
||||||
"""
|
"""
|
||||||
return self._r
|
if self._index is None:
|
||||||
|
raise ValueError("Halo index `hindex` has not been set.")
|
||||||
|
return self._index
|
||||||
|
|
||||||
@r.setter
|
@index.setter
|
||||||
def r(self, r):
|
def index(self, n):
|
||||||
"""Sets `r`. Again checks the shape."""
|
"""Sets the halo index, making sure it is an integer."""
|
||||||
if not isinstance(r, numpy.ndarray) and r.ndim == 1:
|
if n is not None and not (isinstance(n, (int, numpy.int64)) and n > 0):
|
||||||
raise TypeError("`r` must be a 1-dimensional array.")
|
raise ValueError("Halo index `index` must be an integer > 0.")
|
||||||
if not numpy.all(r >= 0):
|
self._index = n
|
||||||
raise ValueError("`r` larger than zero.")
|
|
||||||
self._r = r
|
@property
|
||||||
|
def rhoc(self):
|
||||||
|
"""
|
||||||
|
The critical density :math:`\rho_c` at this snapshot in box units.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
rhoc : float
|
||||||
|
The critical density.
|
||||||
|
"""
|
||||||
|
if self._rhoc is None:
|
||||||
|
raise ValueError("The critical density `rhoc` has not been set.")
|
||||||
|
return self._rhoc
|
||||||
|
|
||||||
|
@rhoc.setter
|
||||||
|
def rhoc(self, rhoc):
|
||||||
|
"""Sets the critical density. Makes sure it is > 0."""
|
||||||
|
if rhoc is not None and not rhoc > 0:
|
||||||
|
raise ValueError("Critical density `rho_c` must be > 0.")
|
||||||
|
self._rhoc = rhoc
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def total_particle_mass(self):
|
def total_particle_mass(self):
|
||||||
|
@ -435,8 +510,165 @@ class Clump:
|
||||||
"""
|
"""
|
||||||
return numpy.mean(self.pos + self.clump_pos, axis=0)
|
return numpy.mean(self.pos + self.clump_pos, axis=0)
|
||||||
|
|
||||||
|
def enclosed_spherical_mass(self, rmax, rmin=None):
|
||||||
|
"""
|
||||||
|
The enclosed spherical mass between two radii. All quantities remain
|
||||||
|
in the box units.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
rmax : float
|
||||||
|
The maximum radial distance.
|
||||||
|
rmin : float, optional
|
||||||
|
The minimum radial distance. By default the radial distance of the
|
||||||
|
closest particle.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
M_enclosed : float
|
||||||
|
The enclosed mass.
|
||||||
|
"""
|
||||||
|
rmin = self.rmin if rmin is None else rmin
|
||||||
|
return numpy.sum(self.m[(self.r >= rmin) & (self.r <= rmax)])
|
||||||
|
|
||||||
|
def enclosed_spherical_density(self, rmax, rmin=None):
|
||||||
|
"""
|
||||||
|
The enclosed spherical density between two radii. All quantities
|
||||||
|
remain in box units.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
rmax : float
|
||||||
|
The maximum radial distance.
|
||||||
|
rmin : float, optional
|
||||||
|
The minimum radial distance. By default the radial distance of the
|
||||||
|
closest particle.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
rho_enclosed : float
|
||||||
|
The enclosed density.
|
||||||
|
"""
|
||||||
|
rmin = self.rmin if rmin is None else rmin
|
||||||
|
M = self.enclosed_spherical_mass(rmax, rmin)
|
||||||
|
V = 4 * numpy.pi / 3 * (rmax**3 - rmin**3)
|
||||||
|
return M / V
|
||||||
|
|
||||||
|
def radius_enclosed_overdensity(self, delta):
|
||||||
|
r"""
|
||||||
|
Radius of where the mean enclosed spherical density reaches a multiple
|
||||||
|
of the critical radius at a given redshift `self.rho_c`. Returns
|
||||||
|
`numpy.nan` if the fit does not converge. Note that `rhoc` must be in
|
||||||
|
box units!
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
delta : int or float
|
||||||
|
The :math:`\delta_{\rm x}` parameters where :math:`\mathrm{x}` is
|
||||||
|
the overdensity multiple.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
rx : float
|
||||||
|
The radius where the enclosed density reaches required value.
|
||||||
|
"""
|
||||||
|
# Loss function to minimise
|
||||||
|
def loss(r):
|
||||||
|
return abs(self.enclosed_spherical_density(r, self.rmin)
|
||||||
|
- delta * self.rhoc)
|
||||||
|
|
||||||
|
res = minimize_scalar(loss, bounds=(self.rmin, self.rmax),
|
||||||
|
method='bounded')
|
||||||
|
return res.x if res.success else numpy.nan
|
||||||
|
|
||||||
|
@property
|
||||||
|
def r200(self):
|
||||||
|
r"""
|
||||||
|
The radius at which the mean spherical density reaches 200 times
|
||||||
|
the critical density, :math:`R_{200c}`. Returns `numpy.nan` if the
|
||||||
|
estimate fails.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
r200 : float
|
||||||
|
The R200c radius
|
||||||
|
"""
|
||||||
|
return self.radius_enclosed_overdensity(200)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def r178(self):
|
||||||
|
r"""
|
||||||
|
The radius at which the mean spherical density reaches 178 times
|
||||||
|
the critical density, :math:`R_{178c}`. Returns `numpy.nan` if the
|
||||||
|
estimate fails.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
r178 : float
|
||||||
|
The R178c radius
|
||||||
|
"""
|
||||||
|
return self.radius_enclosed_overdensity(178)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def r500(self):
|
||||||
|
r"""
|
||||||
|
The radius at which the mean spherical density reaches 500 times
|
||||||
|
the critical density, :math:`R_{500c}`. Returns `numpy.nan` if the
|
||||||
|
estimate fails.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
r500 : float
|
||||||
|
The R500c radius
|
||||||
|
"""
|
||||||
|
return self.radius_enclosed_overdensity(500)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def m200(self):
|
||||||
|
r"""
|
||||||
|
The mass enclosed within the :math:`R_{200c}` region, obtained from
|
||||||
|
`self.r200`. Returns `numpy.nan` if the radius estimate fails.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
m200 : float
|
||||||
|
The M200 mass
|
||||||
|
"""
|
||||||
|
r200 = self.radius_enclosed_overdensity(200)
|
||||||
|
return self.enclosed_spherical_mass(r200)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def m178(self):
|
||||||
|
r"""
|
||||||
|
The mass enclosed within the :math:`R_{178c}` region, obtained from
|
||||||
|
`self.r178`. This is approximately the virial mass, though this notion
|
||||||
|
depends on the dynamical state of the clump. Returns `numpy.nan` if
|
||||||
|
the radius estimate fails.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
m178 : float
|
||||||
|
The M178 mass
|
||||||
|
"""
|
||||||
|
r178 = self.radius_enclosed_overdensity(178)
|
||||||
|
return self.enclosed_spherical_mass(r178)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def m500(self):
|
||||||
|
r"""
|
||||||
|
The mass enclosed within the :math:`R_{500c}` region, obtained from
|
||||||
|
`self.r500`. Returns `numpy.nan` if the radius estimate fails.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
m500 : float
|
||||||
|
The M500 mass
|
||||||
|
"""
|
||||||
|
r500 = self.radius_enclosed_overdensity(500)
|
||||||
|
return self.enclosed_spherical_mass(r500)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_arrays(cls, particles, clump):
|
def from_arrays(cls, particles, clump, rhoc=None):
|
||||||
"""
|
"""
|
||||||
Initialises `Halo` from `particles` containing the relevant particle
|
Initialises `Halo` from `particles` containing the relevant particle
|
||||||
information and its `clump` information.
|
information and its `clump` information.
|
||||||
|
@ -452,14 +684,15 @@ class Clump:
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
halo : `Halo`
|
clump : `Clump`
|
||||||
An initialised halo object.
|
An initialised clump object.
|
||||||
"""
|
"""
|
||||||
x, y, z, m = (particles[p] for p in ["x", "y", "z", "M"])
|
x, y, z, m = (particles[p] for p in ["x", "y", "z", "M"])
|
||||||
x0, y0, z0, cl_mass = (
|
x0, y0, z0, cl_mass, hindex = (
|
||||||
clump[p] for p in ["peak_x", "peak_y", "peak_z", "mass_cl"])
|
clump[p] for p in ["peak_x", "peak_y", "peak_z", "mass_cl",
|
||||||
|
"index"])
|
||||||
try:
|
try:
|
||||||
vx, vy, vz = (particles[p] for p in ["vx", "vy", "vz"])
|
vx, vy, vz = (particles[p] for p in ["vx", "vy", "vz"])
|
||||||
except ValueError:
|
except ValueError:
|
||||||
vx, vy, vz = None, None, None
|
vx, vy, vz = None, None, None
|
||||||
return cls(x, y, z, m, x0, y0, z0, cl_mass, vx, vy, vz)
|
return cls(x, y, z, m, x0, y0, z0, cl_mass, vx, vy, vz, hindex, rhoc)
|
||||||
|
|
|
@ -43,7 +43,8 @@ class NFWProfile:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def profile(self, r, Rs, rho0):
|
@staticmethod
|
||||||
|
def profile(r, Rs, rho0):
|
||||||
r"""
|
r"""
|
||||||
Halo profile evaluated at :math:`r`.
|
Halo profile evaluated at :math:`r`.
|
||||||
|
|
||||||
|
@ -64,7 +65,8 @@ class NFWProfile:
|
||||||
x = r / Rs
|
x = r / Rs
|
||||||
return rho0 / (x * (1 + x)**2)
|
return rho0 / (x * (1 + x)**2)
|
||||||
|
|
||||||
def logprofile(self, r, Rs, rho0):
|
@staticmethod
|
||||||
|
def logprofile(r, Rs, rho0):
|
||||||
r"""
|
r"""
|
||||||
Natural logarithm of the halo profile evaluated at :math:`r`.
|
Natural logarithm of the halo profile evaluated at :math:`r`.
|
||||||
|
|
||||||
|
@ -85,7 +87,8 @@ class NFWProfile:
|
||||||
x = r / Rs
|
x = r / Rs
|
||||||
return numpy.log(rho0) - numpy.log(x) - 2 * numpy.log(1 + x)
|
return numpy.log(rho0) - numpy.log(x) - 2 * numpy.log(1 + x)
|
||||||
|
|
||||||
def enclosed_mass(self, r, Rs, rho0):
|
@staticmethod
|
||||||
|
def enclosed_mass(r, Rs, rho0):
|
||||||
r"""
|
r"""
|
||||||
Enclosed mass of a NFW profile in radius :math:`r`.
|
Enclosed mass of a NFW profile in radius :math:`r`.
|
||||||
|
|
||||||
|
@ -198,16 +201,18 @@ class NFWProfile:
|
||||||
|
|
||||||
class NFWPosterior(NFWProfile):
|
class NFWPosterior(NFWProfile):
|
||||||
r"""
|
r"""
|
||||||
Posterior of for fitting the NFW profile in the range specified by the
|
Posterior for fitting the NFW profile in the range specified by the
|
||||||
closest and further particle. The likelihood is calculated as
|
closest particle and the :math:`r_{200c}` radius. The likelihood is
|
||||||
|
calculated as
|
||||||
|
|
||||||
.. math::
|
.. math::
|
||||||
\frac{4\pi r^2 \rho(r)} {M(r_\min, r_\max)} \frac{m}{M / N}
|
\frac{4\pi r^2 \rho(r)} {M(r_{\min} r_{200c})} \frac{m}{M / N}
|
||||||
|
|
||||||
where :math:`M(r_\min, r_\max)` is the enclosed mass between the closest
|
where :math:`M(r_{\min} r_{200c}))` is the NFW enclosed mass between the
|
||||||
and further particle as expected from a NFW profile, :math:`m` is the
|
closest particle and the :math:`r_{200c}` radius, :math:`m` is the particle
|
||||||
particle mass, :math:`M` is the sum of the particle masses and :math:`N`
|
mass, :math:`M` is the sum of the particle masses and :math:`N` is the
|
||||||
is the number of particles.
|
number of particles. Calculated only using particles within the
|
||||||
|
above-mentioned range.
|
||||||
|
|
||||||
Paramaters
|
Paramaters
|
||||||
----------
|
----------
|
||||||
|
@ -215,10 +220,12 @@ class NFWPosterior(NFWProfile):
|
||||||
Clump object containing the particles and clump information.
|
Clump object containing the particles and clump information.
|
||||||
"""
|
"""
|
||||||
_clump = None
|
_clump = None
|
||||||
_N = None
|
_binsguess = 10
|
||||||
|
_r = None
|
||||||
|
_Npart = None
|
||||||
|
_m = None
|
||||||
_rmin = None
|
_rmin = None
|
||||||
_rmax = None
|
_rmax = None
|
||||||
_binsguess = 10
|
|
||||||
|
|
||||||
def __init__(self, clump):
|
def __init__(self, clump):
|
||||||
# Initialise the NFW profile
|
# Initialise the NFW profile
|
||||||
|
@ -228,7 +235,8 @@ class NFWPosterior(NFWProfile):
|
||||||
@property
|
@property
|
||||||
def clump(self):
|
def clump(self):
|
||||||
"""
|
"""
|
||||||
Clump object.
|
Clump object containig all particles, i.e. ones beyond :math:`R_{200c}`
|
||||||
|
as well.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
|
@ -237,44 +245,49 @@ class NFWPosterior(NFWProfile):
|
||||||
"""
|
"""
|
||||||
return self._clump
|
return self._clump
|
||||||
|
|
||||||
@clump.setter
|
|
||||||
def clump(self, clump):
|
|
||||||
"""Sets `clump` and precalculates useful things."""
|
|
||||||
if not isinstance(clump, Clump):
|
|
||||||
raise TypeError(
|
|
||||||
"`clump` must be :py:class:`csiborgtools.fits.Clump` type. "
|
|
||||||
"Currently `{}`".format(type(clump)))
|
|
||||||
self._clump = clump
|
|
||||||
# Set here the rest of the radial info
|
|
||||||
self._rmax = numpy.max(self.r)
|
|
||||||
# r_min could be zero if particle in the potential minimum.
|
|
||||||
# Set it to the closest particle that is at distance > 0
|
|
||||||
self._rmin = numpy.sort(self.r[self.r > 0])[0]
|
|
||||||
self._logrmin = numpy.log(self.rmin)
|
|
||||||
self._logrmax = numpy.log(self.rmax)
|
|
||||||
self._logprior_volume = numpy.log(self._logrmax - self._logrmin)
|
|
||||||
self._N = self.r.size
|
|
||||||
# Precalculate useful things
|
|
||||||
self._logMtot = numpy.log(numpy.sum(self.clump.m))
|
|
||||||
gamma = 4 * numpy.pi * self.r**2 * self.clump.m * self.N
|
|
||||||
self._ll0 = numpy.sum(numpy.log(gamma)) - self.N * self._logMtot
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def r(self):
|
def r(self):
|
||||||
"""
|
r"""
|
||||||
Radial distance of particles.
|
Radial distance of particles used to fit the NFW profile, i.e. the ones
|
||||||
|
whose radial distance is less than :math:`R_{\rm 200c}`.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
r : 1-dimensional array
|
r : 1-dimensional array
|
||||||
Radial distance of particles.
|
Array of shape `(n_particles, )`.
|
||||||
"""
|
"""
|
||||||
return self.clump.r
|
return self._r
|
||||||
|
|
||||||
|
@property
|
||||||
|
def Npart(self):
|
||||||
|
r"""
|
||||||
|
Number of particles used to fit the NFW profile, i.e. the ones
|
||||||
|
whose radial distance is less than :math:`R_{\rm 200c}`.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Npart : int
|
||||||
|
Number of particles.
|
||||||
|
"""
|
||||||
|
return self._Npart
|
||||||
|
|
||||||
|
@property
|
||||||
|
def m(self):
|
||||||
|
r"""
|
||||||
|
Mass of particles used to fit the NFW profile, i.e. the ones
|
||||||
|
whose radial distance is less than :math:`R_{\rm 200c}`.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
r : 1-dimensional array
|
||||||
|
Array of shape `(n_particles, )`.
|
||||||
|
"""
|
||||||
|
return self._m
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def rmin(self):
|
def rmin(self):
|
||||||
"""
|
"""
|
||||||
Minimum radial distance of a particle belonging to this clump.
|
The minimum radial distance of a particle.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
|
@ -285,30 +298,50 @@ class NFWPosterior(NFWProfile):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def rmax(self):
|
def rmax(self):
|
||||||
"""
|
r"""
|
||||||
Maximum radial distance of a particle belonging to this clump.
|
The maximum radial distance used to fit the profile, here takem to be
|
||||||
|
the :math:`R_{\rm 200c}`.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
rmin : float
|
rmax : float
|
||||||
The maximum distance.
|
The R200c radius.
|
||||||
"""
|
"""
|
||||||
return self._rmax
|
return self._rmax
|
||||||
|
|
||||||
@property
|
@clump.setter
|
||||||
def N(self):
|
def clump(self, clump):
|
||||||
"""
|
"""Sets `clump` and precalculates useful things."""
|
||||||
The number of particles in this clump.
|
if not isinstance(clump, Clump):
|
||||||
|
raise TypeError(
|
||||||
Returns
|
"`clump` must be :py:class:`csiborgtools.fits.Clump` type. "
|
||||||
-------
|
"Currently `{}`".format(type(clump)))
|
||||||
N : int
|
self._clump = clump
|
||||||
Number of particles.
|
# The minimum separation
|
||||||
"""
|
rmin = self.clump.rmin
|
||||||
return self._N
|
rmax = self.clump.r200
|
||||||
|
# Set the distances
|
||||||
|
self._rmin = rmin
|
||||||
|
self._rmax = rmax
|
||||||
|
# Set particles that will be used to fit the halo
|
||||||
|
mask_r200 = (self.clump.r >= rmin) & (self.clump.r <= rmax)
|
||||||
|
self._r = self.clump.r[mask_r200]
|
||||||
|
self._m = self.clump.m[mask_r200]
|
||||||
|
self._Npart = self._r.size
|
||||||
|
# Ensure that the minimum separation is > 0 for finite log
|
||||||
|
if self.rmin > 0:
|
||||||
|
self._logrmin = numpy.log10(self.rmin)
|
||||||
|
else:
|
||||||
|
self._logrmin = numpy.log10(numpy.min(self.r[self.r > 0]))
|
||||||
|
self._logrmax = numpy.log10(self.rmax)
|
||||||
|
self._logprior_volume = numpy.log(self._logrmax - self._logrmin)
|
||||||
|
# Precalculate useful things
|
||||||
|
self._logMtot = numpy.log(numpy.sum(self.m))
|
||||||
|
gamma = 4 * numpy.pi * self.r**2 * self.m * self.Npart
|
||||||
|
self._ll0 = numpy.sum(numpy.log(gamma)) - self.Npart * self._logMtot
|
||||||
|
|
||||||
def rho0_from_logRs(self, logRs):
|
def rho0_from_logRs(self, logRs):
|
||||||
"""
|
r"""
|
||||||
Obtain :math:`\rho_0` of the NFW profile from the integral constraint
|
Obtain :math:`\rho_0` of the NFW profile from the integral constraint
|
||||||
on total mass. Calculated as the ratio between the total particle mass
|
on total mass. Calculated as the ratio between the total particle mass
|
||||||
and the enclosed NFW profile mass.
|
and the enclosed NFW profile mass.
|
||||||
|
@ -324,8 +357,8 @@ class NFWPosterior(NFWProfile):
|
||||||
The NFW density parameter.
|
The NFW density parameter.
|
||||||
"""
|
"""
|
||||||
Mtot = numpy.exp(self._logMtot)
|
Mtot = numpy.exp(self._logMtot)
|
||||||
Rs = numpy.exp(logRs)
|
Mnfw_norm = self.bounded_enclosed_mass(self.rmin, self.rmax,
|
||||||
Mnfw_norm = self.bounded_enclosed_mass(self.rmin, self.rmax, Rs, 1)
|
10**logRs, 1)
|
||||||
return Mtot / Mnfw_norm
|
return Mtot / Mnfw_norm
|
||||||
|
|
||||||
def logprior(self, logRs):
|
def logprior(self, logRs):
|
||||||
|
@ -360,11 +393,12 @@ class NFWPosterior(NFWProfile):
|
||||||
ll : float
|
ll : float
|
||||||
The logarithmic likelihood.
|
The logarithmic likelihood.
|
||||||
"""
|
"""
|
||||||
Rs = numpy.exp(logRs)
|
Rs = 10**logRs
|
||||||
# Expected enclosed mass from a NFW
|
# Expected enclosed mass from a NFW
|
||||||
Mnfw = self.bounded_enclosed_mass(self.rmin, self.rmax, Rs, 1)
|
Mnfw = self.bounded_enclosed_mass(self.rmin, self.rmax,
|
||||||
|
Rs, 1)
|
||||||
ll = self._ll0 + numpy.sum(self.logprofile(self.r, Rs, 1))
|
ll = self._ll0 + numpy.sum(self.logprofile(self.r, Rs, 1))
|
||||||
return ll - self.N * numpy.log(Mnfw)
|
return ll - self.Npart * numpy.log(Mnfw)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def initlogRs(self):
|
def initlogRs(self):
|
||||||
|
@ -378,7 +412,8 @@ class NFWPosterior(NFWProfile):
|
||||||
initlogRs : float
|
initlogRs : float
|
||||||
The initial guess of :math:`\log R_{\rm s}`.
|
The initial guess of :math:`\log R_{\rm s}`.
|
||||||
"""
|
"""
|
||||||
bins = numpy.linspace(self.rmin, self.rmax, self._binsguess)
|
bins = numpy.linspace(self.rmin, self.rmax,
|
||||||
|
self._binsguess)
|
||||||
counts, edges = numpy.histogram(self.r, bins)
|
counts, edges = numpy.histogram(self.r, bins)
|
||||||
return numpy.log(edges[numpy.argmax(counts)])
|
return numpy.log(edges[numpy.argmax(counts)])
|
||||||
|
|
||||||
|
@ -401,61 +436,19 @@ class NFWPosterior(NFWProfile):
|
||||||
return - numpy.infty
|
return - numpy.infty
|
||||||
return self.loglikelihood(logRs) + lp
|
return self.loglikelihood(logRs) + lp
|
||||||
|
|
||||||
def hamiltonian(self, logRs):
|
|
||||||
"""
|
|
||||||
Negative logarithmic posterior (i.e. the Hamiltonian).
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
logRs : float
|
|
||||||
Logarithmic scale factor in units matching the coordinates.
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
neg_lpost : float
|
|
||||||
The Hamiltonian.
|
|
||||||
"""
|
|
||||||
return - self(logRs)
|
|
||||||
|
|
||||||
def maxpost_logRs(self):
|
def maxpost_logRs(self):
|
||||||
r"""
|
r"""
|
||||||
Maximum a-posterio estimate of the scale radius :math:`\log R_{\rm s}`.
|
Maximum a-posterio estimate of the scale radius :math:`\log R_{\rm s}`.
|
||||||
|
Returns the scale radius if the fit converged, otherwise `numpy.nan`.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
res : `scipy.optimize.OptimizeResult`
|
res : float
|
||||||
Optimisation result.
|
The scale radius.
|
||||||
"""
|
"""
|
||||||
bounds = (self._logrmin, self._logrmax)
|
# Loss function to optimize
|
||||||
return minimize_scalar(
|
def loss(logRs):
|
||||||
self.hamiltonian, bounds=bounds, method='bounded')
|
return - self(logRs)
|
||||||
|
res = minimize_scalar(loss, bounds=(self._logrmin, self._logrmax),
|
||||||
@classmethod
|
method='bounded')
|
||||||
def from_coords(cls, x, y, z, m, x0, y0, z0):
|
return res.x if res.success else numpy.nan
|
||||||
"""
|
|
||||||
Initiate `NFWPosterior` from a set of Cartesian coordinates.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
x : 1-dimensional array
|
|
||||||
Particle coordinates along the x-axis.
|
|
||||||
y : 1-dimensional array
|
|
||||||
Particle coordinates along the y-axis.
|
|
||||||
z : 1-dimensional array
|
|
||||||
Particle coordinates along the z-axis.
|
|
||||||
m : 1-dimensional array
|
|
||||||
Particle masses.
|
|
||||||
x0 : float
|
|
||||||
Halo center coordinate along the x-axis.
|
|
||||||
y0 : float
|
|
||||||
Halo center coordinate along the y-axis.
|
|
||||||
z0 : float
|
|
||||||
Halo center coordinate along the z-axis.
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
post : `NFWPosterior`
|
|
||||||
Initiated `NFWPosterior` instance.
|
|
||||||
"""
|
|
||||||
r = numpy.sqrt((x - x0)**2 + (y - y0)**2 + (z - z0)**2)
|
|
||||||
return cls(r, m)
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ from .readsim import (get_csiborg_ids, get_sim_path, get_snapshots, # noqa
|
||||||
get_snapshot_path, read_info, nparts_to_start_ind, # noqa
|
get_snapshot_path, read_info, nparts_to_start_ind, # noqa
|
||||||
open_particle, open_unbinding, read_particle, # noqa
|
open_particle, open_unbinding, read_particle, # noqa
|
||||||
drop_zero_indx, # noqa
|
drop_zero_indx, # noqa
|
||||||
read_clumpid, read_clumps, read_mmain) # noqa
|
read_clumpid, read_clumps, read_mmain, # noqa
|
||||||
|
merge_mmain_to_clumps) # noqa
|
||||||
from .readobs import (read_planck2015, read_2mpp) # noqa
|
from .readobs import (read_planck2015, read_2mpp) # noqa
|
||||||
from .outsim import (dump_split, combine_splits) # noqa
|
from .outsim import (dump_split, combine_splits) # noqa
|
||||||
|
|
|
@ -85,9 +85,6 @@ def combine_splits(Nsplits, Nsim, Nsnap, outdir, cols_add, remove_splits=False,
|
||||||
out : structured array
|
out : structured array
|
||||||
Clump array with appended results from the splits.
|
Clump array with appended results from the splits.
|
||||||
"""
|
"""
|
||||||
# Will be grabbing these columns from each split
|
|
||||||
cols_add = [("npart", I64), ("totpartmass", F64), ("logRs", F64),
|
|
||||||
("rho0", F64)]
|
|
||||||
# Load clumps to see how many there are and will add to this array
|
# Load clumps to see how many there are and will add to this array
|
||||||
simpath = get_sim_path(Nsim)
|
simpath = get_sim_path(Nsim)
|
||||||
clumps = read_clumps(Nsnap, simpath, cols=None)
|
clumps = read_clumps(Nsnap, simpath, cols=None)
|
||||||
|
|
|
@ -23,7 +23,7 @@ from os.path import (join, isfile)
|
||||||
from glob import glob
|
from glob import glob
|
||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
|
|
||||||
from ..utils import cols_to_structured
|
from ..utils import (cols_to_structured, add_columns)
|
||||||
|
|
||||||
|
|
||||||
F16 = numpy.float16
|
F16 = numpy.float16
|
||||||
|
@ -495,3 +495,30 @@ def read_mmain(n, srcdir, fname="Mmain_{}.npy"):
|
||||||
out[name] = arr[:, i]
|
out[name] = arr[:, i]
|
||||||
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def merge_mmain_to_clumps(clumps, mmain):
|
||||||
|
"""
|
||||||
|
Merge columns from the `mmain` files to the `clump` file, matches them
|
||||||
|
by their halo index while assuming that the indices `index` in both arrays
|
||||||
|
are sorted.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
clumps : structured array
|
||||||
|
Clumps structured array.
|
||||||
|
mmain : structured array
|
||||||
|
Parent halo array whose information is to be merged into `clumps`.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
out : structured array
|
||||||
|
Array with added columns.
|
||||||
|
"""
|
||||||
|
X = numpy.full((clumps.size, 2), numpy.nan)
|
||||||
|
# Mask of which clumps have a mmain index
|
||||||
|
mask = numpy.isin(clumps["index"], mmain["index"])
|
||||||
|
|
||||||
|
X[mask, 0] = mmain["mass_cl"]
|
||||||
|
X[mask, 1] = mmain["sub_frac"]
|
||||||
|
return add_columns(clumps, X, ["mass_mmain", "sub_frac"])
|
||||||
|
|
|
@ -16,24 +16,16 @@
|
||||||
Simulation box unit transformations.
|
Simulation box unit transformations.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import numpy
|
||||||
from astropy.cosmology import LambdaCDM
|
from astropy.cosmology import LambdaCDM
|
||||||
from astropy import (constants, units)
|
from astropy import (constants, units)
|
||||||
from ..io import read_info
|
from ..io import read_info
|
||||||
|
|
||||||
|
|
||||||
# Conversion factors
|
|
||||||
MSUNCGS = constants.M_sun.cgs.value
|
|
||||||
KPC_TO_CM = 3.0856775814913673e+21
|
|
||||||
PI = 3.1415926535897932384626433
|
|
||||||
|
|
||||||
|
|
||||||
class BoxUnits:
|
class BoxUnits:
|
||||||
r"""
|
r"""
|
||||||
Box units class for converting between box and physical units.
|
Box units class for converting between box and physical units.
|
||||||
|
|
||||||
TODO: check factors of :math:`a` in mass and density transformations
|
|
||||||
|
|
||||||
Paramaters
|
Paramaters
|
||||||
----------
|
----------
|
||||||
Nsnap : int
|
Nsnap : int
|
||||||
|
@ -41,6 +33,7 @@ class BoxUnits:
|
||||||
simpath : str
|
simpath : str
|
||||||
Path to the simulation where its snapshot index folders are stored.
|
Path to the simulation where its snapshot index folders are stored.
|
||||||
"""
|
"""
|
||||||
|
_cosmo = None
|
||||||
|
|
||||||
def __init__(self, Nsnap, simpath):
|
def __init__(self, Nsnap, simpath):
|
||||||
"""
|
"""
|
||||||
|
@ -51,21 +44,105 @@ class BoxUnits:
|
||||||
"omega_m", "omega_l", "omega_k", "omega_b",
|
"omega_m", "omega_l", "omega_k", "omega_b",
|
||||||
"unit_l", "unit_d", "unit_t"]
|
"unit_l", "unit_d", "unit_t"]
|
||||||
for par in pars:
|
for par in pars:
|
||||||
setattr(self, par, float(info[par]))
|
setattr(self, "_" + par, float(info[par]))
|
||||||
|
|
||||||
self.h = self.H0 / 100
|
self._cosmo = LambdaCDM(H0=self._H0, Om0=self._omega_m,
|
||||||
self.cosmo = LambdaCDM(H0=self.H0, Om0=self.omega_m, Ode0=self.omega_l,
|
Ode0=self._omega_l, Tcmb0=2.725 * units.K,
|
||||||
Tcmb0=2.725 * units.K, Ob0=self.omega_b)
|
Ob0=self._omega_b)
|
||||||
# Constants in box units
|
self._Msuncgs = constants.M_sun.cgs.value # Solar mass in grams
|
||||||
self.G = constants.G.cgs.value * (self.unit_d * self.unit_t ** 2)
|
|
||||||
self.H0 = self.H0 * 1e5 / (1e3 * KPC_TO_CM) * self.unit_t
|
@property
|
||||||
self.c = constants.c.cgs.value * self.unit_t / self.unit_l
|
def cosmo(self):
|
||||||
self.rho_crit = 3 * self.H0 ** 2 / (8 * PI * self.G)
|
"""
|
||||||
|
The box cosmology.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
cosmo : `astropy.cosmology.LambdaCDM`
|
||||||
|
The CSiBORG cosmology.
|
||||||
|
"""
|
||||||
|
return self._cosmo
|
||||||
|
|
||||||
|
@property
|
||||||
|
def H0(self):
|
||||||
|
r"""
|
||||||
|
The Hubble parameter at the time of the snapshot
|
||||||
|
in :math:`\mathrm{Mpc} / \mathrm{km} / \mathrm{s}`.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
H0 : float
|
||||||
|
Hubble constant.
|
||||||
|
"""
|
||||||
|
return self._H0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def h(self):
|
||||||
|
r"""
|
||||||
|
The little 'h` parameter at the time of the snapshot.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
h : float
|
||||||
|
The little h
|
||||||
|
"""
|
||||||
|
return self._H0 / 100
|
||||||
|
|
||||||
|
@property
|
||||||
|
def box_G(self):
|
||||||
|
"""
|
||||||
|
Gravitational constant :math:`G` in box units. Given everything else
|
||||||
|
it looks like `self.unit_t` is in seconds.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
G : float
|
||||||
|
The gravitational constant.
|
||||||
|
"""
|
||||||
|
return constants.G.cgs.value * (self._unit_d * self._unit_t ** 2)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def box_H0(self):
|
||||||
|
"""
|
||||||
|
Present time Hubble constant :math:`H_0` in box units.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
H0 : float
|
||||||
|
The Hubble constant.
|
||||||
|
"""
|
||||||
|
return self.H0 * 1e5 / units.Mpc.to(units.cm) * self._unit_t
|
||||||
|
|
||||||
|
@property
|
||||||
|
def box_c(self):
|
||||||
|
"""
|
||||||
|
Speed of light in box units.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
c : float
|
||||||
|
The speed of light.
|
||||||
|
"""
|
||||||
|
return constants.c.cgs.value * self._unit_t / self._unit_l
|
||||||
|
|
||||||
|
@property
|
||||||
|
def box_rhoc(self):
|
||||||
|
"""
|
||||||
|
Critical density in box units.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
rhoc : float
|
||||||
|
The critical density.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return 3 * self.box_H0 ** 2 / (8 * numpy.pi * self.box_G)
|
||||||
|
|
||||||
def box2kpc(self, length):
|
def box2kpc(self, length):
|
||||||
r"""
|
r"""
|
||||||
Convert length from box units to :math:`\mathrm{ckpc}` (with
|
Convert length from box units to :math:`\mathrm{ckpc}` (with
|
||||||
:math:`h=0.705`).
|
:math:`h=0.705`). It appears that `self.unit_l` must be in
|
||||||
|
:math:`\mathrm{cm}`.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
|
@ -77,7 +154,7 @@ class BoxUnits:
|
||||||
length : foat
|
length : foat
|
||||||
Length in :math:`\mathrm{ckpc}`
|
Length in :math:`\mathrm{ckpc}`
|
||||||
"""
|
"""
|
||||||
return length * self.unit_l / KPC_TO_CM / self.aexp
|
return length * (self._unit_l / units.kpc.to(units.cm) / self._aexp)
|
||||||
|
|
||||||
def kpc2box(self, length):
|
def kpc2box(self, length):
|
||||||
r"""
|
r"""
|
||||||
|
@ -94,7 +171,7 @@ class BoxUnits:
|
||||||
length : foat
|
length : foat
|
||||||
Length in box units.
|
Length in box units.
|
||||||
"""
|
"""
|
||||||
return length / self.unit_l * KPC_TO_CM * self.aexp
|
return length / (self._unit_l / units.kpc.to(units.cm) / self._aexp)
|
||||||
|
|
||||||
def solarmass2box(self, mass):
|
def solarmass2box(self, mass):
|
||||||
r"""
|
r"""
|
||||||
|
@ -110,11 +187,13 @@ class BoxUnits:
|
||||||
mass : float
|
mass : float
|
||||||
Mass in box units.
|
Mass in box units.
|
||||||
"""
|
"""
|
||||||
return mass / self.unit_d / (self.unit_l**3 / MSUNCGS)
|
return mass / (self._unit_d * self._unit_l**3) * self._Msuncgs
|
||||||
|
|
||||||
def box2solarmass(self, mass):
|
def box2solarmass(self, mass):
|
||||||
r"""
|
r"""
|
||||||
Convert mass from box units to :math:`M_\odot` (with :math:`h=0.705`).
|
Convert mass from box units to :math:`M_\odot` (with :math:`h=0.705`).
|
||||||
|
It appears that `self.unit_d` is density in units of
|
||||||
|
:math:`\mathrm{g}/\mathrm{cm}^3`.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
|
@ -126,7 +205,7 @@ class BoxUnits:
|
||||||
mass : float
|
mass : float
|
||||||
Mass in :math:`M_\odot`.
|
Mass in :math:`M_\odot`.
|
||||||
"""
|
"""
|
||||||
return mass * self.unit_d * self.unit_l**3 / MSUNCGS
|
return mass * (self._unit_d * self._unit_l**3) / self._Msuncgs
|
||||||
|
|
||||||
def box2dens(self, density):
|
def box2dens(self, density):
|
||||||
r"""
|
r"""
|
||||||
|
@ -143,7 +222,8 @@ class BoxUnits:
|
||||||
density : float
|
density : float
|
||||||
Density in :math:`M_\odot / \mathrm{pc}^3`.
|
Density in :math:`M_\odot / \mathrm{pc}^3`.
|
||||||
"""
|
"""
|
||||||
return density * self.unit_d / MSUNCGS * (KPC_TO_CM * 1e-3)**3
|
return (density * self._unit_d / self._Msuncgs
|
||||||
|
* (units.pc.to(units.cm))**3)
|
||||||
|
|
||||||
def dens2box(self, density):
|
def dens2box(self, density):
|
||||||
r"""
|
r"""
|
||||||
|
@ -160,4 +240,5 @@ class BoxUnits:
|
||||||
density : float
|
density : float
|
||||||
Density in box units.
|
Density in box units.
|
||||||
"""
|
"""
|
||||||
return density / self.unit_d * MSUNCGS / (KPC_TO_CM * 1e-3)**3
|
return (density / self._unit_d * self._Msuncgs
|
||||||
|
/ (units.pc.to(units.cm))**3)
|
||||||
|
|
|
@ -44,7 +44,10 @@ nproc = comm.Get_size()
|
||||||
dumpdir = utils.dumpdir
|
dumpdir = utils.dumpdir
|
||||||
loaddir = join(utils.dumpdir, "temp")
|
loaddir = join(utils.dumpdir, "temp")
|
||||||
cols_collect = [("npart", I64), ("totpartmass", F64), ("logRs", F64),
|
cols_collect = [("npart", I64), ("totpartmass", F64), ("logRs", F64),
|
||||||
("rho0", F64)]
|
("rho0", F64), ("rmin", F64), ("rmax", F64),
|
||||||
|
("r200", F64), ("r178", F64), ("r500", F64),
|
||||||
|
("m200", F64), ("m178", F64), ("m500", F64)]
|
||||||
|
|
||||||
# NOTE later loop over sims too
|
# NOTE later loop over sims too
|
||||||
Nsim = Nsims[0]
|
Nsim = Nsims[0]
|
||||||
|
|
||||||
|
@ -56,7 +59,9 @@ for Nsplit in jobs:
|
||||||
|
|
||||||
N = clumps.size
|
N = clumps.size
|
||||||
cols = [("index", I64), ("npart", I64), ("totpartmass", F64),
|
cols = [("index", I64), ("npart", I64), ("totpartmass", F64),
|
||||||
("logRs", F64), ("rho0", F64)]
|
("logRs", F64), ("rho0", F64), ("rmin", F64), ("rmax", F64),
|
||||||
|
("r200", F64), ("r178", F64), ("r500", F64),
|
||||||
|
("m200", F64), ("m178", F64), ("m500", F64)]
|
||||||
out = csiborgtools.utils.cols_to_structured(N, cols)
|
out = csiborgtools.utils.cols_to_structured(N, cols)
|
||||||
out["index"] = clumps["index"]
|
out["index"] = clumps["index"]
|
||||||
|
|
||||||
|
@ -65,15 +70,25 @@ for Nsplit in jobs:
|
||||||
xs = csiborgtools.fits.pick_single_clump(n, parts, part_clumps, clumps)
|
xs = csiborgtools.fits.pick_single_clump(n, parts, part_clumps, clumps)
|
||||||
clump = csiborgtools.fits.Clump.from_arrays(*xs)
|
clump = csiborgtools.fits.Clump.from_arrays(*xs)
|
||||||
out["npart"][n] = clump.Npart
|
out["npart"][n] = clump.Npart
|
||||||
|
out["rmin"][n] = clump.rmin
|
||||||
|
out["rmax"][n] = clump.rmax
|
||||||
out["totpartmass"][n] = clump.total_particle_mass
|
out["totpartmass"][n] = clump.total_particle_mass
|
||||||
|
out["r200"][n] = clump.r200
|
||||||
|
out["r178"][n] = clump.r178
|
||||||
|
out["r500"][n] = clump.r500
|
||||||
|
out["m200"][n] = clump.m200
|
||||||
|
out["m178"][n] = clump.m178
|
||||||
|
out["m500"][n] = clump.m200
|
||||||
|
|
||||||
# NFW profile fit
|
# NFW profile fit
|
||||||
if clump.Npart > 10:
|
if clump.Npart > 10 and numpy.isfinite(out["r200"][n]):
|
||||||
|
# NOTE here it calculates the r200 again, but its fast so does not
|
||||||
|
# matter anyway.
|
||||||
nfwpost = csiborgtools.fits.NFWPosterior(clump)
|
nfwpost = csiborgtools.fits.NFWPosterior(clump)
|
||||||
logRs = nfwpost.maxpost_logRs()
|
logRs = nfwpost.maxpost_logRs()
|
||||||
if logRs.success:
|
if not numpy.isnan(logRs):
|
||||||
out["logRs"][n] = logRs.x
|
out["logRs"][n] = logRs
|
||||||
out["rho0"][n] = nfwpost.rho0_from_logRs(logRs.x)
|
out["rho0"][n] = nfwpost.rho0_from_logRs(logRs)
|
||||||
|
|
||||||
csiborgtools.io.dump_split(out, Nsplit, Nsim, Nsnap, dumpdir)
|
csiborgtools.io.dump_split(out, Nsplit, Nsim, Nsnap, dumpdir)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue