Skip to content

Commit

Permalink
Merge pull request #64 from dangilman/gcs
Browse files Browse the repository at this point in the history
Gcs
  • Loading branch information
dangilman authored Oct 29, 2024
2 parents 5f6bde1 + 66cbe6d commit cd6571b
Show file tree
Hide file tree
Showing 14 changed files with 342 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from lenstronomy.LensModel.Profiles.gaussian import Gaussian
import numpy as np

class Gaussian(Halo):
class GaussianHalo(Halo):
"""
The base class for a Gaussian fluctuation
Expand All @@ -19,8 +19,8 @@ def __init__(self, mass, x, y, r3d, z,
self._concentration_class = concentration_class
self._truncation_class = truncation_class
mdef = 'GAUSSIAN_KAPPA'
super(Gaussian, self).__init__(mass, x, y, r3d, mdef, z, sub_flag,
lens_cosmo_instance, args, unique_tag, fixed_position=True)
super(GaussianHalo, self).__init__(mass, x, y, r3d, mdef, z, sub_flag,
lens_cosmo_instance, args, unique_tag, fixed_position=True)

@property
def profile_args(self):
Expand Down
72 changes: 72 additions & 0 deletions pyHalo/Halos/HaloModels/powerlaw.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,75 @@ def z_eval(self):
Returns the redshift at which to evaluate the concentration-mass relation
"""
return self.z

class GlobularCluster(Halo):

def __init__(self, mass, x, y, z, lens_cosmo_instance, args, unique_tag):
"""
A mass profile model for a globular cluster
:param mass: the total mass of the GC
:param x: x coordinate [arcsec]
:param y: y coordinate [arcsec]
:param z: redshift
:param lens_cosmo_instance: an instance of LensCosmo class
:param args: keyword arguments for the profile, must contain 'gamma', 'r_core_fraction', 'gc_size_lightyear'
which are the logarithmic profile slope outside the core, the core size in units of the GC size, and the gc size
in light years. The size and mass are related by
gc_size = gc_size_lightyear (m / 10^5)^1/3
The mass is the total mass inside a sphere with radius gc_size
:param unique_tag:
"""
self._prof = SPLCORE()
self._lens_cosmo = lens_cosmo_instance
mdef = 'SPL_CORE'
super(GlobularCluster, self).__init__(mass, x, y, None, mdef, z, True,
lens_cosmo_instance, args, unique_tag)

@property
def lenstronomy_params(self):
"""
See documentation in base class (Halos/halo_base.py)
"""
if not hasattr(self, '_lenstronomy_args'):

profile_args = self.profile_args
rho0 = profile_args['rho0']
r_core_arcsec = profile_args['r_core_arcsec']
gamma = profile_args['gamma']
sigma0 = rho0 * r_core_arcsec
self._lenstronomy_args = [{'sigma0': sigma0, 'gamma': gamma, 'center_x': self.x, 'center_y': self.y,
'r_core': r_core_arcsec}]
return self._lenstronomy_args, None

@property
def profile_args(self):
"""
See documentation in base class (Halos/halo_base.py)
"""
if not hasattr(self, '_profile_args'):
kpc_per_arcsec = self._lens_cosmo.cosmo.kpc_proper_per_asec(self.z)
gamma = self._args['gamma']
r_core_fraction = self._args['r_core_fraction']
gc_size_lightyear_0 = self._args['gc_size_lightyear']
sigma_crit_mpc = self._lens_cosmo.get_sigma_crit_lensing(self.z, self._lens_cosmo.z_source)
sigma_crit_arcsec = sigma_crit_mpc * (0.001 * kpc_per_arcsec) ** 2
kpc_per_lightyear = 0.3 * 1e-3
gc_size = gc_size_lightyear_0 * (self.mass / 10 ** 5) ** (1 / 3) * kpc_per_lightyear
r_core_arcsec = r_core_fraction * gc_size
rho0 = self.mass / self._prof.mass_3d(gc_size, sigma_crit_arcsec, r_core_arcsec, gamma)
self._profile_args = {'rho0': rho0, 'gc_size': gc_size, 'gamma': gamma, 'r_core_arcsec': r_core_arcsec}
return self._profile_args

@property
def lenstronomy_ID(self):
"""
See documentation in base class (Halos/halo_base.py)
"""
return ['SPL_CORE']

@property
def z_eval(self):
"""
Returns the redshift at which to evalate the concentration-mass relation
"""
return self.z
2 changes: 0 additions & 2 deletions pyHalo/Halos/lens_cosmo.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,12 @@ def __init__(self, z_lens=None, z_source=None, cosmology=None,
if cosmology is None:
from pyHalo.Cosmology.cosmology import Cosmology
cosmology = Cosmology()

self.cosmo = cosmology
self._arcsec = 2 * numpy.pi / 360 / 3600
self.h = self.cosmo.h
# critical density of the universe in M_sun h^2 Mpc^-3
rhoc = un.Quantity(self.cosmo.astropy.critical_density(0), unit=un.Msun / un.Mpc ** 3).value
self.rhoc = rhoc / self.cosmo.h ** 2

if z_lens is not None and z_source is not None:
# critical density for lensing in units M_sun * Mpc ^ -2
self.sigma_crit_lensing = self.get_sigma_crit_lensing(z_lens, z_source)
Expand Down
4 changes: 2 additions & 2 deletions pyHalo/PresetModels/wdm.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ def WDM(z_lens, z_source, log_mc, sigma_sub=0.025, log_mlow=6., log_mhigh=10., l
:param truncation_model_fieldhalos: the truncation model applied to field halos, see truncation_models for a
complete list
:param kwargs_truncation_model_fieldhalos: keyword arguments for the truncation model applied to field halos
:param infall_redshift_model: a string that specifies that infall redshift sampling distribution, see the LensCosmo
class for details
:param infall_redshift_model: a string that specifies that infall redshift sampling distribution, options are
HYBRID_INFALL (accounts for subhalos and sub-subhalos) and DIRECT_INFALL (only subhalos)
:param kwargs_infall_model: keyword arguments for the infall redshift model
:param shmf_log_slope: the logarithmic slope of the subhalo mass function pivoting around 10^8 M_sun
:param cone_opening_angle_arcsec: the opening angle of the rendering volume in arcsec
Expand Down
41 changes: 41 additions & 0 deletions pyHalo/Rendering/MassFunctions/gaussian.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from copy import deepcopy
import numpy as np


class Gaussian(object):
"""
This class generates masses from a delta function normalized with respect to a
background density, a mass, and a volume
"""
name = 'GAUSSIAN'
def __init__(self, n, mean, sigma, draw_poisson=True, *args, **kwargs):
"""
:param n: normalization, also equal to the number of objects
:param mean: mass of objects to render
:param sigma: rendering volume
:param draw poisson: whether or not to draw from a poisson distribution
"""
self.mean = mean
self.sigma = sigma
self.n_mean = n
self.first_moment = self.mean
self.draw_poisson = draw_poisson

def draw(self):

"""
:return: an array of masses
"""

if self.draw_poisson:
n = int(np.random.poisson(self.n_mean))
else:
n = int(np.round(self.n_mean))

if n > 0:
log10_mass = np.random.normal(self.mean, self.sigma, n)
m = 10 ** log10_mass
return np.array(m)
else:
return np.array([])
1 change: 1 addition & 0 deletions pyHalo/Rendering/MassFunctions/stucker.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,4 @@ def _supp_mscale(a, b, c, frac=0.5):
# This is just here, to tell the fitting function when it goes out of bounds
return np.nan
return a / (frac**(1./c) - 1.)**(1./b)

1 change: 0 additions & 1 deletion pyHalo/concentration_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ def preset_concentration_models(model_name, kwargs_model=None):
kwargs_model_return = {}
else:
kwargs_model_return = deepcopy(kwargs_model)

if model_name == 'DIEMERJOYCE19':
return ConcentrationDiemerJoyce, kwargs_model_return
elif model_name == 'LUDLOW2016':
Expand Down
79 changes: 70 additions & 9 deletions pyHalo/realization_extensions.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import numpy as np
from pyHalo.Halos.HaloModels.powerlaw import PowerLawSubhalo, PowerLawFieldHalo
from pyHalo.Halos.HaloModels.powerlaw import PowerLawSubhalo, PowerLawFieldHalo, GlobularCluster
from pyHalo.Halos.HaloModels.generalized_nfw import GeneralNFWSubhalo, GeneralNFWFieldHalo
from pyHalo.single_realization import Realization
from pyHalo.Halos.HaloModels.gaussian import Gaussian
from pyHalo.Halos.HaloModels.gaussianhalo import GaussianHalo
from pyHalo.Rendering.correlated_structure import CorrelatedStructure
from pyHalo.Rendering.MassFunctions.delta_function import DeltaFunction
from pyHalo.Rendering.MassFunctions.gaussian import Gaussian
from pyHalo.Cosmology.geometry import Geometry
from pyHalo.utilities import generate_lens_plane_redshifts, mask_annular
from pyHalo.Rendering.SpatialDistributions.uniform import Uniform
Expand Down Expand Up @@ -32,6 +33,66 @@ def __init__(self, realization):

self._realization = realization

def add_globular_clusters(self, log10_mgc_mean, log10_mgc_sigma, rendering_radius_arcsec, gc_profile_args,
galaxy_Re=6.0, host_halo_mass=10**13.3, f=3.4e-5, center_x=0, center_y=0):
"""
Add globular clusters at z = z_lens to a realization sampling from a log-normal mass distribution
:param log10_mgc_mean: the median log10(mass) of the GC's
:param log10_mgc_sigma: the standard deviation of the Gaussian mass function for log10(m)
:param rendering_radius_arcsec [arcsec]: sets the area around (center_x, center_y) where the GC's appear; GC's are
distributed uniformly in a circule centered at (center_x, center_y) with this radius
:param gc_profile_args: the keyword arguments for the GC mass profile, must include "gamma", "gc_size_lightyear",
"r_core_fraction", or the logarithmic power law slope, the size of the gc in light years, and the size of the core
relative to the size of the GC
:param galaxy_Re: galaxy half-light radius
:param host_halo_mass: total mass of the host DM halo
:param f: the fraction M_gc / M_host from https://arxiv.org/pdf/1602.01105
:param coordinate_center_x: center of rendering area in arcsec
:param coordinate_center_y: center of rendering area in arcsec
:return: an instance of Realization that includes the GC's
"""

n = self.number_globular_clusters(log10_mgc_mean, log10_mgc_sigma, rendering_radius_arcsec, galaxy_Re,
host_halo_mass, f)
mfunc = Gaussian(n, log10_mgc_mean, log10_mgc_sigma)
m = mfunc.draw()
z = self._realization.lens_cosmo.z_lens
uniform_spatial_distribution = Uniform(rendering_radius_arcsec, self._realization.geometry)
x, y = uniform_spatial_distribution.draw(len(m), z, center_x, center_y)
lens_cosmo = self._realization.lens_cosmo
GCS = []
for (m_gc, x_center_gc, y_center_gc) in zip(m, x, y):
unique_tag = np.random.rand()
profile = GlobularCluster(m_gc, x_center_gc, y_center_gc, lens_cosmo.z_lens, lens_cosmo,
gc_profile_args, unique_tag)
GCS.append(profile)
gcs_realization = Realization.from_halos(GCS, self._realization.lens_cosmo,
self._realization.kwargs_halo_model,
self._realization.apply_mass_sheet_correction,
self._realization.rendering_classes,
self._realization._rendering_center_x,
self._realization._rendering_center_y,
self._realization.geometry)
new_realization = self._realization.join(gcs_realization)
return new_realization

def number_globular_clusters(self, log10_mgc_mean, log10_mgc_sigma, rendering_radius_arcsec,
galaxy_Re=10.0, host_halo_mass=10**13.3, f=3.4e-5):
"""
:return:
"""
m_gc = f * host_halo_mass
area = np.pi * (6 * galaxy_Re) ** 2 # physical kpc^2
surface_mass_density = m_gc / area
z = self._realization.lens_cosmo.z_lens
kpc_per_arcsec = self._realization.lens_cosmo.cosmo.kpc_proper_per_asec(z)
# assuming log-normal mass function
integral = np.exp(np.log(10 ** log10_mgc_mean) + np.log(10 ** log10_mgc_sigma) ** 2 / 2)
mass_in_gc = np.pi * surface_mass_density * (rendering_radius_arcsec * kpc_per_arcsec) ** 2
n = int(mass_in_gc / integral)
return n

def toSIDM(self, cross_section):
"""
Expand Down Expand Up @@ -496,14 +557,14 @@ def _get_fluctuation_halos(realization, fluctuation_amplitude, fluctuation_size,

args_fluc=[{'amp': amps[i], 'sigma': sigs[i], 'center_x': xs[i], 'center_y': ys[i]} for i in range(len(amps))]
masses = np.absolute(amps)
fluctuations = [Gaussian(masses[i], xs[i], ys[i], None, realization.lens_cosmo.z_lens,
True, realization.lens_cosmo, args_fluc[i],
truncation_class=None, concentration_class=None,
unique_tag=np.random.rand()) for i in range(len(amps))]
fluctuations = [GaussianHalo(masses[i], xs[i], ys[i], None, realization.lens_cosmo.z_lens,
True, realization.lens_cosmo, args_fluc[i],
truncation_class=None, concentration_class=None,
unique_tag=np.random.rand()) for i in range(len(amps))]

return fluctuations

def corr_kappa_with_mask(kappa_map, map_size, r, mu, apply_mask=True, r_min=0.5, r_max=1.5, normalization=False):
def corr_kappa_with_mask(kappa_map, map_size, r, mu, apply_mask=True, r_min=0.5, r_max=1.5, normalization=False):

"""
This function computes the two-point correlation function from a convergence map.
Expand All @@ -515,7 +576,7 @@ def corr_kappa_with_mask(kappa_map, map_size, r, mu, apply_mask=True, r_min=0.5,
of the angles between 0 and 180 degrees.
:param apply_mask: if True, apply the mask on the convergence map.
:param r_min: inner radius of mask in units of grid coordinates
:param r_max: outer radius of mask in units of grid coordinates. If r_max = None, the size of the convergence map's outside
:param r_max: outer radius of mask in units of grid coordinates. If r_max = None, the size of the convergence map's outside
boundary becomes the mask's outer boundary.
:param normalization: if True, apply normalization to the correlation function.
:return: the two-point correlation function on the (mu, r) coordinate grid.
Expand Down Expand Up @@ -553,7 +614,7 @@ def corr_kappa_with_mask(kappa_map, map_size, r, mu, apply_mask=True, r_min=0.5,
mask_interp = RectBivariateSpline(X_, Y_, mask, kx=1, ky=1, s=0)
else:
mask = np.ones(XX_.shape)

kappa_interp = RectBivariateSpline(X_, Y_, kappa_map, kx=5, ky=5, s=0)

corr = np.zeros((r.shape[0], mu.shape[0]))
Expand Down
4 changes: 2 additions & 2 deletions pyHalo/single_realization.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from pyHalo.Halos.HaloModels.PTMass import PTMass
from pyHalo.Halos.HaloModels.ULDM import ULDMFieldHalo, ULDMSubhalo
from pyHalo.Halos.HaloModels.NFW_core_trunc import TNFWCFieldHaloSIDM, TNFWCSubhaloSIDM
from pyHalo.Halos.HaloModels.gaussian import Gaussian
from pyHalo.Halos.HaloModels.gaussianhalo import GaussianHalo
import numpy as np
from copy import deepcopy

Expand Down Expand Up @@ -677,7 +677,7 @@ def _load_halo_model(mass, x, y, r3d, mdef, z, is_subhalo,
else:
model = ULDMFieldHalo
elif mdef == 'GAUSSIAN_KAPPA':
model = Gaussian
model = GaussianHalo
elif mdef == 'GNFW':
if is_subhalo:
model = GeneralNFWSubhalo
Expand Down
2 changes: 1 addition & 1 deletion pyHalo/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ def compute_comoving_ray_path(x_coordinate, y_coordinate, lens_model, kwargs_len
alpha_x_start, alpha_y_start = x_coordinate, y_coordinate
for zi in all_redshifts_sorted:

x_start, y_start, alpha_x_start, alpha_y_start = lens_model.lens_model.ray_shooting_partial(x_start, y_start,
x_start, y_start, alpha_x_start, alpha_y_start = lens_model.lens_model.ray_shooting_partial_comoving(x_start, y_start,
alpha_x_start, alpha_y_start,
z_start, zi, kwargs_lens)
d = float(comoving_distance_calc(zi))
Expand Down
8 changes: 4 additions & 4 deletions tests/test_halos/test_gaussian.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import numpy.testing as npt
import numpy as np
from pyHalo.Halos.HaloModels.gaussian import Gaussian
from pyHalo.Halos.HaloModels.gaussianhalo import GaussianHalo
from pyHalo.Halos.lens_cosmo import LensCosmo
from pyHalo.Cosmology.cosmology import Cosmology
from astropy.cosmology import FlatLambdaCDM
Expand All @@ -22,9 +22,9 @@ def setup_method(self):
lens_cosmo = LensCosmo(z, 2., cosmo)
profile_args = {'amp':1,'sigma':1,'center_x':1.0,'center_y':1.0}
sub_flag = False
self.halo = Gaussian(mass, x, y, r3d, z,
sub_flag, lens_cosmo,
profile_args, None, None, unique_tag=np.random.rand())
self.halo = GaussianHalo(mass, x, y, r3d, z,
sub_flag, lens_cosmo,
profile_args, None, None, unique_tag=np.random.rand())

def test_lenstronomy_params(self):

Expand Down
52 changes: 52 additions & 0 deletions tests/test_halos/test_globular_clusters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import numpy.testing as npt
from pyHalo.Halos.lens_cosmo import LensCosmo
from lenstronomy.LensModel.Profiles.splcore import SPLCORE
from pyHalo.Halos.HaloModels.powerlaw import GlobularCluster
import pytest
import numpy as np


class TestSPLCORE(object):

def setup_method(self):

self.zhalo = 0.5
self.zsource = 2.0
self.lens_cosmo = LensCosmo(self.zhalo, self.zsource, None)
self.splcore = SPLCORE()

def test_lenstronomy_ID(self):

mass = 10 ** 5
args = {'gamma': 2.5,
'r_core_fraction': 0.05,
'gc_size_lightyear': 100}
profile = GlobularCluster(mass, 0.0, 0.0, self.zhalo, self.lens_cosmo,
args, 1)
lenstronomy_ID = profile.lenstronomy_ID
npt.assert_string_equal(lenstronomy_ID[0], 'SPL_CORE')

def test_lenstronomy_args(self):

logM = 5.0
mass = 10 ** logM
args = {'gamma': 2.5,
'r_core_fraction': 0.05,
'gc_size_lightyear': 100}
profile = GlobularCluster(mass, 0.0, 0.0, self.zhalo, self.lens_cosmo,
args, 1)
lenstronomy_args, _ = profile.lenstronomy_params
profile_lenstronomy = SPLCORE()
profile_args = profile.profile_args
gc_size = profile_args['gc_size']
rho0 = profile_args['rho0']
r_core = profile_args['r_core_arcsec']
gamma = profile_args['gamma']
mass = profile_lenstronomy.mass_3d(gc_size, rho0, r_core, gamma)
sigma_crit_mpc = self.lens_cosmo.get_sigma_crit_lensing(profile.z, self.lens_cosmo.z_source)
kpc_per_arcsec = self.lens_cosmo.cosmo.kpc_proper_per_asec(profile.z)
sigma_crit_arcsec = sigma_crit_mpc * (0.001 * kpc_per_arcsec) ** 2
npt.assert_almost_equal(logM, np.log10(mass * sigma_crit_arcsec))

if __name__ == '__main__':
pytest.main()
Loading

0 comments on commit cd6571b

Please sign in to comment.