class TestLensCosmo(object): """ tests the UnitManager class routines """ def setup(self): z_L = 0.8 z_S = 3.0 from astropy.cosmology import FlatLambdaCDM cosmo = FlatLambdaCDM(H0=70, Om0=0.3, Ob0=0.05) self.nfwParam = NFWParam() def test_rho0_c(self): c = 4 rho0 = self.nfwParam.rho0_c(c) c_out = self.nfwParam.c_rho0(rho0) npt.assert_almost_equal(c_out, c, decimal=3) def test_profileMain(self): M = 10**(13.5) z = 0.5 r200, rho0, c, Rs = self.nfwParam.profileMain(M, z) c_ = self.nfwParam.c_M_z(M, z) r200_ = self.nfwParam.r200_M(M) rho0_ = self.nfwParam.rho0_c(c) Rs_ = r200_ / c_ npt.assert_almost_equal(c_, c, decimal=5) npt.assert_almost_equal(r200_, r200, decimal=5) npt.assert_almost_equal(rho0_, rho0, decimal=5) npt.assert_almost_equal(Rs_, Rs, decimal=5)
class LensCosmo(object): """ class to manage the physical units and distances present in a single plane lens with fixed input cosmology """ def __init__(self, z_lens, z_source, cosmo=None): """ :param z_lens: redshift of lens :param z_source: redshift of source :param cosmo: astropy.cosmology instance """ self.z_lens = z_lens self.z_source = z_source self.background = Background(cosmo=cosmo) self.nfw_param = NFWParam() def a_z(self, z): """ convert redshift into scale factor :param z: redshift :return: scale factor """ return 1. / (1. + z) @property def h(self): return self.background.cosmo.H(0).value / 100. @property def D_d(self): """ :return: angular diameter distance to the deflector [Mpc] """ return self.background.D_xy(0, self.z_lens) @property def D_s(self): """ :return: angular diameter distance to the source [Mpc] """ return self.background.D_xy(0, self.z_source) @property def D_ds(self): """ :return: angular diameter distance from deflector to source [Mpc] """ return self.background.D_xy(self.z_lens, self.z_source) @property def D_dt(self): """ :return: time delay distance [Mpc] """ return (1 + self.z_lens) * self.D_d * self.D_s / self.D_ds @property def epsilon_crit(self): """ returns the critical projected mass density in units of M_sun/Mpc^2 (physical units) :return: critical projected mass density """ if not hasattr(self, '_Epsilon_Crit'): const_SI = const.c**2 / (4 * np.pi * const.G ) #c^2/(4*pi*G) in units of [kg/m] conversion = const.Mpc / const.M_sun # converts [kg/m] to [M_sun/Mpc] factor = const_SI * conversion #c^2/(4*pi*G) in units of [M_sun/Mpc] self._Epsilon_Crit = self.D_s / ( self.D_d * self.D_ds) * factor #[M_sun/Mpc^2] return self._Epsilon_Crit def phys2arcsec_lens(self, phys): """ convert physical Mpc into arc seconds :param phys: physical distance [Mpc] :return: angular diameter [arcsec] """ return phys / self.D_d / const.arcsec def arcsec2phys_lens(self, arcsec): """ convert angular to physical quantities for lens plane :param arcsec: angular size at lens plane [arcsec] :return: physical size at lens plane [Mpc] """ return arcsec * const.arcsec * self.D_d def arcsec2phys_source(self, arcsec): """ convert angular to physical quantities for source plane :param arcsec: angular size at source plane [arcsec] :return: physical size at source plane [Mpc] """ return arcsec * const.arcsec * self.D_s def kappa2proj_mass(self, kappa): """ convert convergence to projected mass M_sun/Mpc^2 :param kappa: lensing convergence :return: projected mass [M_sun/Mpc^2] """ return kappa * self.epsilon_crit def mass_in_theta_E(self, theta_E): """ mass within Einstein radius (area * epsilon crit) [M_sun] :param theta_E: Einstein radius [arcsec] :return: mass within Einstein radius [M_sun] """ mass = self.arcsec2phys_lens(theta_E)**2 * np.pi * self.epsilon_crit return mass def mass_in_coin(self, theta_E): """ :param theta_E: Einstein radius [arcsec] :return: mass in coin calculated in mean density of the universe """ chi_L = self.background.T_xy(0, self.z_lens) chi_S = self.background.T_xy(0, self.z_source) return 1. / 3 * np.pi * ( chi_L * theta_E * const.arcsec )**2 * chi_S * self.background.rho_crit #[M_sun/Mpc**3] def time_delay_units(self, fermat_pot, kappa_ext=0): """ :param fermat_pot: in units of arcsec^2 (e.g. Fermat potential) :param kappa_ext: unit-less :return: time delay in days """ D_dt = self.D_dt / (1. - kappa_ext) * const.Mpc # eqn 7 in Suyu et al. return D_dt / const.c * fermat_pot / const.day_s * const.arcsec**2 # * self.arcsec2phys_lens(1.)**2 def time_delay2fermat_pot(self, dt): """ :param dt: time delay in units of days :return: Fermat potential in units arcsec**2 for a given cosmology """ D_dt = self.D_dt * const.Mpc return dt * const.c * const.day_s / D_dt / const.arcsec**2 def nfw_angle2physical(self, Rs_angle, alpha_Rs): """ converts the angular parameters into the physical ones for an NFW profile :param alpha_Rs: observed bending angle at the scale radius in units of arcsec :param Rs: scale radius in units of arcsec :return: M200, r200, Rs_physical, c """ Rs = Rs_angle * const.arcsec * self.D_d theta_scaled = alpha_Rs * self.epsilon_crit * self.D_d * const.arcsec rho0 = theta_scaled / (4 * Rs**2 * (1 + np.log(1. / 2.))) rho0_com = rho0 / self.h**2 * self.a_z(self.z_lens)**3 c = self.nfw_param.c_rho0(rho0_com) r200 = c * Rs M200 = self.nfw_param.M_r200( r200 * self.h / self.a_z(self.z_lens)) / self.h return rho0, Rs, c, r200, M200 def nfw_physical2angle(self, M, c): """ converts the physical mass and concentration parameter of an NFW profile into the lensing quantities :param M: mass enclosed 200 rho_crit in units of M_sun :param c: NFW concentration parameter (r200/r_s) :return: alpha_Rs (observed bending angle at the scale radius, Rs_angle (angle at scale radius) (in units of arcsec) """ rho0, Rs, r200 = self.nfwParam_physical(M, c) Rs_angle = Rs / self.D_d / const.arcsec # Rs in arcsec alpha_Rs = rho0 * (4 * Rs**2 * (1 + np.log(1. / 2.))) return Rs_angle, alpha_Rs / self.epsilon_crit / self.D_d / const.arcsec def nfwParam_physical(self, M, c): """ returns the NFW parameters in physical units :param M: physical mass in M_sun :param c: concentration :return: """ r200 = self.nfw_param.r200_M(M * self.h) / self.h * self.a_z( self.z_lens) # physical radius r200 rho0 = self.nfw_param.rho0_c(c) * self.h**2 / self.a_z( self.z_lens)**3 # physical density in M_sun/Mpc**3 Rs = r200 / c return rho0, Rs, r200 def sis_theta_E2sigma_v(self, theta_E): """ converts the lensing Einstein radius into a physical velocity dispersion :param theta_E: Einstein radius (in arcsec) :return: velocity dispersion in units (km/s) """ v_sigma_c2 = theta_E * const.arcsec / (4 * np.pi) * self.D_s / self.D_ds return np.sqrt(v_sigma_c2) * const.c / 1000 def sis_sigma_v2theta_E(self, v_sigma): """ converts the velocity dispersion into an Einstein radius for a SIS profile :param v_sigma: velocity dispersion (km/s) :return: theta_E (arcsec) """ theta_E = 4 * np.pi * ( v_sigma * 1000. / const.c)**2 * self.D_ds / self.D_s / const.arcsec return theta_E
class TestLensCosmo(object): """ tests the UnitManager class routines """ def setup(self): cosmo = FlatLambdaCDM(H0=70, Om0=0.3, Ob0=0.05) self.nfwParam = NFWParam(cosmo=cosmo) self.z = 0.5 # needed fixed redshift for the inversion function def test_rho0_c(self): c = 4 rho0 = self.nfwParam.rho0_c(c, z=self.z) c_out = self.nfwParam.c_rho0(rho0, z=self.z) npt.assert_almost_equal(c_out, c, decimal=3) def test_rhoc_z(self): z = 0 rho0_z = self.nfwParam.rhoc_z(z=z) npt.assert_almost_equal(self.nfwParam.rhoc * (1 + z)**3, rho0_z) def test_M200(self): M200 = self.nfwParam.M200(rs=1, rho0=1, c=1) npt.assert_almost_equal(M200, 2.4271590540348216, decimal=5) def test_profileMain(self): M = 10**13.5 z = 0.5 r200, rho0, c, Rs = self.nfwParam.nfw_Mz(M, z) c_ = self.nfwParam.c_M_z(M, z) r200_ = self.nfwParam.r200_M(M, z) rho0_ = self.nfwParam.rho0_c(c, z) Rs_ = r200_ / c_ npt.assert_almost_equal(c_, c, decimal=5) npt.assert_almost_equal(r200_, r200, decimal=5) npt.assert_almost_equal(rho0_, rho0, decimal=5) npt.assert_almost_equal(Rs_, Rs, decimal=5) def test_against_colossus(self): """ This test class asks to get the same parameters back as colossus: https://bdiemer.bitbucket.io/colossus/index.html """ cosmo = FlatLambdaCDM(H0=70, Om0=0.285, Ob0=0.05) nfw_param = NFWParam(cosmo=cosmo) from colossus.cosmology import cosmology as cosmology_colossus from colossus.halo.profile_nfw import NFWProfile colossus_kwargs = { 'H0': 70, 'Om0': 0.285, 'Ob0': 0.05, 'ns': 0.96, 'sigma8': 0.82 } colossus = cosmology_colossus.setCosmology('custom', colossus_kwargs) m200 = 10**8 c = 17. zvals = np.linspace(0.0, 2, 50) h = 0.7 for z in zvals: nfw_colossus = NFWProfile(m200 * h, z, mdef='200c') rhos_colossus, rs_colossus = nfw_colossus.fundamentalParameters( m200 * h, c, z, mdef='200c') r200_colossus = rs_colossus * c # according to colossus documentation the density is in physical units[M h^2/kpc^3] and distance [kpc/h] rs_colossus *= h**-1 rhos_colossus *= h**2 r200_lenstronomy = nfw_param.r200_M(m200 * h, z) / h # physical radius r200 rs_lenstronomy = r200_lenstronomy / c rhos_lenstronomy = nfw_param.rho0_c( c, z) * h**2 # physical density in M_sun/Mpc**3 # convert Mpc to kpc rhos_lenstronomy *= 1000**-3 rs_lenstronomy *= 1000 npt.assert_almost_equal(rs_lenstronomy / rs_colossus, 1, decimal=3) npt.assert_almost_equal(rhos_lenstronomy / rhos_colossus, 1, decimal=3)
class LensCosmo(object): """ class to manage the physical units and distances present in a single plane lens with fixed input cosmology """ def __init__(self, z_lens, z_source, cosmo=None): """ :param z_lens: redshift of lens :param z_source: redshift of source :param cosmo: astropy.cosmology instance """ self.z_lens = z_lens self.z_source = z_source self.background = Background(cosmo=cosmo) self.nfw_param = NFWParam(cosmo=cosmo) def a_z(self, z): """ convert redshift into scale factor :param z: redshift :return: scale factor """ return 1. / (1. + z) @property def h(self): return self.background.cosmo.H(0).value / 100. @property def dd(self): """ :return: angular diameter distance to the deflector [Mpc] """ return self.background.d_xy(0, self.z_lens) @property def ds(self): """ :return: angular diameter distance to the source [Mpc] """ return self.background.d_xy(0, self.z_source) @property def dds(self): """ :return: angular diameter distance from deflector to source [Mpc] """ return self.background.d_xy(self.z_lens, self.z_source) @property def ddt(self): """ :return: time delay distance [Mpc] """ return (1 + self.z_lens) * self.dd * self.ds / self.dds @property def sigma_crit(self): """ returns the critical projected lensing mass density in units of M_sun/Mpc^2 :return: critical projected lensing mass density """ if not hasattr(self, '_sigma_crit_mpc'): const_SI = const.c**2 / (4 * np.pi * const.G ) # c^2/(4*pi*G) in units of [kg/m] conversion = const.Mpc / const.M_sun # converts [kg/m] to [M_sun/Mpc] factor = const_SI * conversion # c^2/(4*pi*G) in units of [M_sun/Mpc] self._sigma_crit_mpc = self.ds / ( self.dd * self.dds) * factor # [M_sun/Mpc^2] return self._sigma_crit_mpc @property def sigma_crit_angle(self): """ returns the critical surface density in units of M_sun/arcsec^2 (in physical solar mass units) when provided a physical mass per physical Mpc^2 :return: critical projected mass density """ if not hasattr(self, '_sigma_crit_arcsec'): const_SI = const.c**2 / (4 * np.pi * const.G ) # c^2/(4*pi*G) in units of [kg/m] conversion = const.Mpc / const.M_sun # converts [kg/m] to [M_sun/Mpc] factor = const_SI * conversion # c^2/(4*pi*G) in units of [M_sun/Mpc] self._sigma_crit_arcsec = self.ds / ( self.dd * self.dds) * factor * ( self.dd * const.arcsec)**2 # [M_sun/arcsec^2] return self._sigma_crit_arcsec def phys2arcsec_lens(self, phys): """ convert physical Mpc into arc seconds :param phys: physical distance [Mpc] :return: angular diameter [arcsec] """ return phys / self.dd / const.arcsec def arcsec2phys_lens(self, arcsec): """ convert angular to physical quantities for lens plane :param arcsec: angular size at lens plane [arcsec] :return: physical size at lens plane [Mpc] """ return arcsec * const.arcsec * self.dd def arcsec2phys_source(self, arcsec): """ convert angular to physical quantities for source plane :param arcsec: angular size at source plane [arcsec] :return: physical size at source plane [Mpc] """ return arcsec * const.arcsec * self.ds def kappa2proj_mass(self, kappa): """ convert convergence to projected mass M_sun/Mpc^2 :param kappa: lensing convergence :return: projected mass [M_sun/Mpc^2] """ return kappa * self.sigma_crit def mass_in_theta_E(self, theta_E): """ mass within Einstein radius (area * epsilon crit) [M_sun] :param theta_E: Einstein radius [arcsec] :return: mass within Einstein radius [M_sun] """ mass = self.arcsec2phys_lens(theta_E)**2 * np.pi * self.sigma_crit return mass def mass_in_coin(self, theta_E): """ :param theta_E: Einstein radius [arcsec] :return: mass in coin calculated in mean density of the universe """ chi_L = self.background.T_xy(0, self.z_lens) chi_S = self.background.T_xy(0, self.z_source) return 1. / 3 * np.pi * ( chi_L * theta_E * const.arcsec )**2 * chi_S * self.background.rho_crit #[M_sun/Mpc**3] def time_delay_units(self, fermat_pot, kappa_ext=0): """ :param fermat_pot: in units of arcsec^2 (e.g. Fermat potential) :param kappa_ext: unit-less external shear not accounted for in the Fermat potential :return: time delay in days """ D_dt = self.ddt * (1. - kappa_ext) * const.Mpc # eqn 7 in Suyu et al. return D_dt / const.c * fermat_pot / const.day_s * const.arcsec**2 # * self.arcsec2phys_lens(1.)**2 def time_delay2fermat_pot(self, dt): """ :param dt: time delay in units of days :return: Fermat potential in units arcsec**2 for a given cosmology """ D_dt = self.ddt * const.Mpc return dt * const.c * const.day_s / D_dt / const.arcsec**2 def nfw_angle2physical(self, Rs_angle, alpha_Rs): """ converts the angular parameters into the physical ones for an NFW profile :param alpha_Rs: observed bending angle at the scale radius in units of arcsec :param Rs: scale radius in units of arcsec :return: rho0, Rs, c, r200, M200 """ Rs = Rs_angle * const.arcsec * self.dd theta_scaled = alpha_Rs * self.sigma_crit * self.dd * const.arcsec rho0 = theta_scaled / (4 * Rs**2 * (1 + np.log(1. / 2.))) rho0_com = rho0 / self.h**2 c = self.nfw_param.c_rho0(rho0_com, self.z_lens) r200 = c * Rs M200 = self.nfw_param.M_r200(r200 * self.h, self.z_lens) / self.h return rho0, Rs, c, r200, M200 def nfw_physical2angle(self, M, c): """ converts the physical mass and concentration parameter of an NFW profile into the lensing quantities :param M: mass enclosed 200 rho_crit in units of M_sun (physical units, meaning no little h) :param c: NFW concentration parameter (r200/r_s) :return: Rs_angle (angle at scale radius) (in units of arcsec), alpha_Rs (observed bending angle at the scale radius """ rho0, Rs, r200 = self.nfwParam_physical(M, c) Rs_angle = Rs / self.dd / const.arcsec # Rs in arcsec alpha_Rs = rho0 * (4 * Rs**2 * (1 + np.log(1. / 2.))) return Rs_angle, alpha_Rs / self.sigma_crit / self.dd / const.arcsec def nfwParam_physical(self, M, c): """ returns the NFW parameters in physical units :param M: physical mass in M_sun :param c: concentration :return: rho0, Rs, r200 """ r200 = self.nfw_param.r200_M( M * self.h, self.z_lens) / self.h # physical radius r200 rho0 = self.nfw_param.rho0_c( c, self.z_lens) * self.h**2 # physical density in M_sun/Mpc**3 Rs = r200 / c return rho0, Rs, r200 def nfw_M_theta_vir(self, M): """ returns virial radius in angular units of arc seconds on the sky :param M: physical mass in M_sun :return: angle (in arc seconds) of the virial radius """ r200 = self.nfw_param.r200_M( M * self.h, self.z_lens) / self.h # physical radius r200 theta_r200 = r200 / self.dd / const.arcsec return theta_r200 def sis_theta_E2sigma_v(self, theta_E): """ converts the lensing Einstein radius into a physical velocity dispersion :param theta_E: Einstein radius (in arcsec) :return: velocity dispersion in units (km/s) """ v_sigma_c2 = theta_E * const.arcsec / (4 * np.pi) * self.ds / self.dds return np.sqrt(v_sigma_c2) * const.c / 1000 def sis_sigma_v2theta_E(self, v_sigma): """ converts the velocity dispersion into an Einstein radius for a SIS profile :param v_sigma: velocity dispersion (km/s) :return: theta_E (arcsec) """ theta_E = 4 * np.pi * (v_sigma * 1000. / const.c)**2 * self.dds / self.ds / const.arcsec return theta_E def uldm_angular2phys(self, kappa_0, theta_c): """ converts the anguar parameters entering the LensModel Uldm() (Ultra Light Dark Matter) class in physical masses, i.e. the total soliton mass and the mass of the particle :param kappa_0: central convergence of profile :param theta_c: core radius (in arcseconds) :return: m_eV_log10, M_sol_log10, the log10 of the masses, m in eV and M in M_sun """ D_Lens = self.dd * 10**6 # in parsec Sigma_c = self.sigma_crit * 10**(-12) # in M_sun / parsec^2 r_c = theta_c * const.arcsec * D_Lens rho0 = 2048 * np.sqrt(0.091) * kappa_0 * Sigma_c / (429 * np.pi * r_c) m_log10 = -22 + 0.5 * np.log10(190 / rho0 * (r_c / 100)**(-4)) M_log10 = 9 + np.log10(160 * 1.4 / r_c) - 2 * (m_log10 + 22) return m_log10, M_log10 def uldm_mphys2angular(self, m_log10, M_log10): """ converts physical ULDM mass in the ones, in angular units, that enter the LensModel Uldm() class :param m_log10: exponent of ULDM mass in eV :param M_log10: exponent of soliton mass in M_sun :return: kappa_0, theta_c, the central convergence and core radius (in arcseconds) """ D_Lens = self.dd * 10**6 # in parsec Sigma_c = self.sigma_crit * 10**(-12) # in M_sun/parsec^2 m22 = 10**(m_log10 + 22) M9 = 10**(M_log10 - 9) r_c = 160 * 1.4 * m22**(-2) * M9**(-1) # core radius in parsec rho0 = 190 * m22**(-2) * (r_c / 100)**( -4) # central density in M_sun/parsec^3 kappa_0 = 429 * np.pi * rho0 * r_c / (2048 * np.sqrt(0.091) * Sigma_c) theta_c = r_c / D_Lens / const.arcsec return kappa_0, theta_c