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.lensCosmo = LensCosmo(z_L, z_S, cosmo=cosmo)

    def test_ang_dist(self):
        npt.assert_almost_equal(self.lensCosmo.ds,
                                1588.9213590743666,
                                decimal=8)
        npt.assert_almost_equal(self.lensCosmo.dd,
                                1548.7055203661785,
                                decimal=8)
        npt.assert_almost_equal(self.lensCosmo.dds,
                                892.0038749095863,
                                decimal=8)

    def test_epsilon_crit(self):
        npt.assert_almost_equal(self.lensCosmo.sigma_crit / 1.9121e+15,
                                1,
                                decimal=3)

    def test_arcsec2phys(self):
        arcsec = np.array([1, 2])  # pixel coordinate from center
        physcoord = self.lensCosmo.arcsec2phys_lens(arcsec)
        npt.assert_almost_equal(physcoord[0], 0.0075083362428338641, decimal=8)
        npt.assert_almost_equal(physcoord[1], 0.015016672485667728, decimal=8)

        physcoord = self.lensCosmo.arcsec2phys_source(arcsec)
        npt.assert_almost_equal(physcoord[0], 0.007703308130864105, decimal=8)
        npt.assert_almost_equal(physcoord[1], 0.01540661626172821, decimal=8)

    def test_phys2arcsec_lens(self):
        phys = 1.
        arc_sec = self.lensCosmo.phys2arcsec_lens(phys)
        phys_new = self.lensCosmo.arcsec2phys_lens(arc_sec)
        npt.assert_almost_equal(phys_new, phys, decimal=8)

    def test_mass_in_phi_E(self):
        phi_E = 1.5
        mass = self.lensCosmo.mass_in_theta_E(phi_E)
        npt.assert_almost_equal(mass, 761967261292.6725, decimal=2)

    def test_kappa2proj_mass(self):
        kappa = 0.5
        mass = self.lensCosmo.kappa2proj_mass(kappa)
        npt.assert_almost_equal(mass,
                                kappa * self.lensCosmo.sigma_crit,
                                decimal=3)

    def test_mass_in_coin(self):
        theta_E = 1.
        m_coin = self.lensCosmo.mass_in_coin(theta_E)
        npt.assert_almost_equal(m_coin, 165279526936.52194, decimal=0)

    def test_D_dt_model(self):
        D_dt = self.lensCosmo.ddt
        npt.assert_almost_equal(D_dt, 4965.660384441859, decimal=8)

    def test_nfw_angle2physical(self):
        Rs_angle = 6.
        alpha_Rs = 1.
        rho0, Rs, c, r200, M200 = self.lensCosmo.nfw_angle2physical(
            Rs_angle, alpha_Rs)
        assert Rs * c == r200

    def test_nfw_physical2angle(self):
        M = 10.**13.5
        c = 4
        Rs_angle, alpha_Rs = self.lensCosmo.nfw_physical2angle(M, c)
        rho0, Rs, c_out, r200, M200 = self.lensCosmo.nfw_angle2physical(
            Rs_angle, alpha_Rs)
        npt.assert_almost_equal(c_out, c, decimal=3)
        npt.assert_almost_equal(np.log10(M200), np.log10(M), decimal=4)

    def test_sis_theta_E2sigma_v(self):
        theta_E = 2.
        sigma_v = self.lensCosmo.sis_theta_E2sigma_v(theta_E)
        theta_E_out = self.lensCosmo.sis_sigma_v2theta_E(sigma_v)
        npt.assert_almost_equal(theta_E_out, theta_E, decimal=5)

    def test_fermat2delays(self):

        fermat_pot = 0.5
        dt_days = self.lensCosmo.time_delay_units(fermat_pot)
        fermat_pot_out = self.lensCosmo.time_delay2fermat_pot(dt_days)
        npt.assert_almost_equal(fermat_pot, fermat_pot_out, decimal=10)

    def test_uldm_angular2phys(self):

        kappa_0, theta_c = 0.1, 3
        mlog10, Mlog10 = self.lensCosmo.uldm_angular2phys(kappa_0, theta_c)
        npt.assert_almost_equal(mlog10, -24.3610006, decimal=5)
        npt.assert_almost_equal(Mlog10, 11.7195843, decimal=5)

    def test_uldm_mphys2angular(self):

        m_log10, M_log10 = -24, 11
        kappa_0, theta_c = self.lensCosmo.uldm_mphys2angular(m_log10, M_log10)
        mcheck, Mcheck = self.lensCosmo.uldm_angular2phys(kappa_0, theta_c)
        npt.assert_almost_equal(mcheck, m_log10, decimal=4)
        npt.assert_almost_equal(Mcheck, M_log10, decimal=4)

    def test_a_z(self):

        a = self.lensCosmo.a_z(z=1)
        npt.assert_almost_equal(a, 0.5)
示例#2
0
class LensModel(object):
    """
    class to handle an arbitrary list of lens models
    """
    def __init__(self,
                 lens_model_list,
                 z_lens=None,
                 z_source=None,
                 lens_redshift_list=None,
                 cosmo=None,
                 multi_plane=False,
                 numerical_alpha_class=None):
        """

        :param lens_model_list: list of strings with lens model names
        :param z_lens: redshift of the deflector (only considered when operating in single plane mode).
        Is only needed for specific functions that require a cosmology.
        :param z_source: redshift of the source: Needed in multi_plane option only,
        not required for the core functionalities in the single plane mode.
        :param lens_redshift_list: list of deflector redshift (corresponding to the lens model list),
        only applicable in multi_plane mode.
        :param cosmo: instance of the astropy cosmology class. If not specified, uses the default cosmology.
        :param multi_plane: bool, if True, uses multi-plane mode. Default is False.
        :param numerical_alpha_class: an instance of a custom class for use in NumericalAlpha() lens model
        (see documentation in Profiles/numerical_alpha)
        """
        self.lens_model_list = lens_model_list
        self.z_lens = z_lens
        self.z_source = z_source
        self.redshift_list = lens_redshift_list
        self.cosmo = cosmo
        self.multi_plane = multi_plane
        if multi_plane is True:
            self.lens_model = MultiPlane(
                z_source,
                lens_model_list,
                lens_redshift_list,
                cosmo=cosmo,
                numerical_alpha_class=numerical_alpha_class)
        else:
            self.lens_model = SinglePlane(
                lens_model_list, numerical_alpha_class=numerical_alpha_class)
        if z_lens is not None and z_source is not None:
            self._lensCosmo = LensCosmo(z_lens, z_source, cosmo=self.cosmo)

    def ray_shooting(self, x, y, kwargs, k=None):
        """
        maps image to source position (inverse deflection)

        :param x: x-position (preferentially arcsec)
        :type x: numpy array
        :param y: y-position (preferentially arcsec)
        :type y: numpy array
        :param kwargs: list of keyword arguments of lens model parameters matching the lens model classes
        :param k: only evaluate the k-th lens model
        :return: source plane positions corresponding to (x, y) in the image plane
        """
        return self.lens_model.ray_shooting(x, y, kwargs, k=k)

    def fermat_potential(self, x_image, y_image, x_source, y_source,
                         kwargs_lens):
        """
        fermat potential (negative sign means earlier arrival time)

        :param x_image: image position
        :param y_image: image position
        :param x_source: source position
        :param y_source: source position
        :param kwargs_lens: list of keyword arguments of lens model parameters matching the lens model classes
        :return: fermat potential in arcsec**2 without geometry term (second part of Eqn 1 in Suyu et al. 2013) as a list
        """
        if hasattr(self.lens_model, 'fermat_potential'):
            return self.lens_model.fermat_potential(x_image, y_image, x_source,
                                                    y_source, kwargs_lens)
        else:
            raise ValueError(
                "Fermat potential is not defined in multi-plane lensing. Please use single plane lens models."
            )

    def arrival_time(self, x_image, y_image, kwargs_lens):
        """

        :param x_image: image position
        :param y_image: image position
        :param kwargs_lens: lens model parameter keyword argument list
        :return:
        """
        if hasattr(self.lens_model, 'arrival_time'):
            arrival_time = self.lens_model.arrival_time(
                x_image, y_image, kwargs_lens)
        else:
            x_source, y_source = self.lens_model.ray_shooting(
                x_image, y_image, kwargs_lens)
            fermat_pot = self.lens_model.fermat_potential(
                x_image, y_image, x_source, y_source, kwargs_lens)
            if not hasattr(self, '_lensCosmo'):
                raise ValueError(
                    "LensModel class was not initalized with lens and source redshifts!"
                )
            arrival_time = self._lensCosmo.time_delay_units(fermat_pot)
        return arrival_time

    def potential(self, x, y, kwargs, k=None):
        """
        lensing potential

        :param x: x-position (preferentially arcsec)
        :type x: numpy array
        :param y: y-position (preferentially arcsec)
        :type y: numpy array
        :param kwargs: list of keyword arguments of lens model parameters matching the lens model classes
        :param k: only evaluate the k-th lens model
        :return: lensing potential in units of arcsec^2
        """
        return self.lens_model.potential(x, y, kwargs, k=k)

    def alpha(self, x, y, kwargs, k=None):
        """
        deflection angles

        :param x: x-position (preferentially arcsec)
        :type x: numpy array
        :param y: y-position (preferentially arcsec)
        :type y: numpy array
        :param kwargs: list of keyword arguments of lens model parameters matching the lens model classes
        :param k: only evaluate the k-th lens model
        :return: deflection angles in units of arcsec
        """
        return self.lens_model.alpha(x, y, kwargs, k=k)

    def hessian(self, x, y, kwargs, k=None):
        """
        hessian matrix

        :param x: x-position (preferentially arcsec)
        :type x: numpy array
        :param y: y-position (preferentially arcsec)
        :type y: numpy array
        :param kwargs: list of keyword arguments of lens model parameters matching the lens model classes
        :param k: only evaluate the k-th lens model
        :return: f_xx, f_xy, f_yy components
        """
        return self.lens_model.hessian(x, y, kwargs, k=k)

    def kappa(self, x, y, kwargs, k=None):
        """
        lensing convergence k = 1/2 laplacian(phi)

        :param x: x-position (preferentially arcsec)
        :type x: numpy array
        :param y: y-position (preferentially arcsec)
        :type y: numpy array
        :param kwargs: list of keyword arguments of lens model parameters matching the lens model classes
        :param k: only evaluate the k-th lens model
        :return: lensing convergence
        """

        f_xx, f_xy, f_yx, f_yy = self.hessian(x, y, kwargs, k=k)
        kappa = 1. / 2 * (f_xx + f_yy)
        return kappa

    def gamma(self, x, y, kwargs, k=None):
        """
        shear computation
        g1 = 1/2(d^2phi/dx^2 - d^2phi/dy^2)
        g2 = d^2phi/dxdy

        :param x: x-position (preferentially arcsec)
        :type x: numpy array
        :param y: y-position (preferentially arcsec)
        :type y: numpy array
        :param kwargs: list of keyword arguments of lens model parameters matching the lens model classes
        :param k: only evaluate the k-th lens model
        :return: gamma1, gamma2
        """

        f_xx, f_xy, f_yx, f_yy = self.hessian(x, y, kwargs, k=k)
        gamma1 = 1. / 2 * (f_xx - f_yy)
        gamma2 = f_xy
        return gamma1, gamma2

    def magnification(self, x, y, kwargs, k=None):
        """
        magnification
        mag = 1/det(A)
        A = 1 - d^2phi/d_ij

        :param x: x-position (preferentially arcsec)
        :type x: numpy array
        :param y: y-position (preferentially arcsec)
        :type y: numpy array
        :param kwargs: list of keyword arguments of lens model parameters matching the lens model classes
        :param k: only evaluate the k-th lens model
        :return: magnification
        """

        f_xx, f_xy, f_yx, f_yy = self.hessian(x, y, kwargs, k=k)
        det_A = (1 - f_xx) * (1 - f_yy) - f_xy * f_yx
        return 1. / det_A  # attention, if dividing by zero

    def flexion(self, x, y, kwargs, diff=0.000001):
        """
        third derivatives (flexion)

        :param x: x-position (preferentially arcsec)
        :type x: numpy array
        :param y: y-position (preferentially arcsec)
        :type y: numpy array
        :param kwargs: list of keyword arguments of lens model parameters matching the lens model classes
        :param diff: numerical differential length of Hessian
        :return: f_xxx, f_xxy, f_xyy, f_yyy
        """
        f_xx, f_xy, f_yx, f_yy = self.hessian(x, y, kwargs)

        f_xx_dx, f_xy_dx, f_yx_dx, f_yy_dx = self.hessian(x + diff, y, kwargs)
        f_xx_dy, f_xy_dy, f_yx_dy, f_yy_dy = self.hessian(x, y + diff, kwargs)

        f_xxx = (f_xx_dx - f_xx) / diff
        f_xxy = (f_xx_dy - f_xx) / diff
        f_xyy = (f_xy_dy - f_xy) / diff
        f_yyy = (f_yy_dy - f_yy) / diff
        return f_xxx, f_xxy, f_xyy, f_yyy
示例#3
0
class LensProp(object):
    """
    this class contains routines to compute time delays, magnification ratios, line of sight velocity dispersions etc
    for a given lens model
    """
    def __init__(self, z_lens, z_source, kwargs_model, cosmo=None):
        """

        :param z_lens: redshift of lens
        :param z_source: redshift of source
        :param kwargs_model: model keyword arguments
        :param cosmo: astropy.cosmology instance
        """
        self.z_d = z_lens
        self.z_s = z_source
        self.lensCosmo = LensCosmo(z_lens, z_source, cosmo=cosmo)
        self.lens_analysis = LensAnalysis(kwargs_model)
        self._lensModelExt = LensModelExtensions(self.lens_analysis.LensModel)
        self.kwargs_options = kwargs_model
        self._kwargs_cosmo = {
            'D_d': self.lensCosmo.D_d,
            'D_s': self.lensCosmo.D_s,
            'D_ds': self.lensCosmo.D_ds
        }

    def time_delays(self, kwargs_lens, kwargs_ps, kappa_ext=0):
        """
        predicts the time delays of the image positions

        :param kwargs_lens: lens model parameters
        :param kwargs_ps: point source parameters
        :param kappa_ext: external convergence (optional)
        :return: time delays at image positions for the fixed cosmology
        """
        fermat_pot = self.lens_analysis.fermat_potential(
            kwargs_lens, kwargs_ps)
        time_delay = self.lensCosmo.time_delay_units(fermat_pot, kappa_ext)
        return time_delay

    def velocity_dispersion(self,
                            kwargs_lens,
                            r_eff,
                            R_slit,
                            dR_slit,
                            psf_fwhm,
                            aniso_param=1,
                            psf_type='GAUSSIAN',
                            moffat_beta=2.6,
                            num_evaluate=1000,
                            kappa_ext=0):
        """
        computes the LOS velocity dispersion of the lens within a slit of size R_slit x dR_slit and seeing psf_fwhm.
        The assumptions are a Hernquist light profile and the spherical power-law lens model at the first position.

        Further information can be found in the AnalyticKinematics() class.

        :param kwargs_lens: lens model parameters
        :param kwargs_lens_light: deflector light parameters
        :param aniso_param: scaled r_ani with respect to the half light radius
        :param r_eff: half light radius, if not provided, will be computed from the lens light model
        :param R_slit: width of the slit
        :param dR_slit: length of the slit
        :param psf_fwhm: full width at half maximum of the seeing (Gaussian form)
        :param psf_type: string, point spread functino type, current support for 'GAUSSIAN' and 'MOFFAT'
        :param moffat_beta: float, beta parameter of Moffat profile
        :param num_evaluate: number of spectral rendering of the light distribution that end up on the slit
        :param kappa_ext: external convergence not accounted in the lens models
        :return: velocity dispersion in units [km/s]
        """
        gamma = kwargs_lens[0]['gamma']
        theta_E = kwargs_lens[0]['theta_E']
        r_ani = aniso_param * r_eff
        analytic_kinematics = AnalyticKinematics(fwhm=psf_fwhm,
                                                 moffat_beta=moffat_beta,
                                                 psf_type=psf_type,
                                                 **self._kwargs_cosmo)
        sigma = analytic_kinematics.vel_disp(gamma,
                                             theta_E,
                                             r_eff,
                                             r_ani,
                                             R_slit,
                                             dR_slit,
                                             rendering_number=num_evaluate)
        sigma *= np.sqrt(1 - kappa_ext)
        return sigma

    def velocity_dispersion_numerical(self,
                                      kwargs_lens,
                                      kwargs_lens_light,
                                      kwargs_anisotropy,
                                      kwargs_aperture,
                                      psf_fwhm,
                                      aperture_type,
                                      anisotropy_model,
                                      r_eff,
                                      psf_type='GAUSSIAN',
                                      moffat_beta=2.6,
                                      kwargs_numerics={},
                                      MGE_light=False,
                                      MGE_mass=False,
                                      lens_model_kinematics_bool=None,
                                      light_model_kinematics_bool=None,
                                      Hernquist_approx=False,
                                      kappa_ext=0):
        """
        Computes the LOS velocity dispersion of the deflector galaxy with arbitrary combinations of light and mass models.
        For a detailed description, visit the description of the Galkin() class.
        Additionaly to executing the Galkin routine, it has an optional Multi-Gaussian-Expansion decomposition of lens
        and light models that do not have a three-dimensional distribution built in, such as Sersic profiles etc.

        The center of all the lens and lens light models that are part of the kinematic estimate must be centered on the
        same point.

        :param kwargs_lens: lens model parameters
        :param kwargs_lens_light: lens light parameters
        :param kwargs_anisotropy: anisotropy parameters (see Galkin module)
        :param kwargs_aperture: aperture parameters (see Galkin module)
        :param psf_fwhm: full width at half maximum of the seeing (Gaussian form)
        :param psf_type: string, point spread functino type, current support for 'GAUSSIAN' and 'MOFFAT'
        :param moffat_beta: float, beta parameter of Moffat profile
        :param aperture_type: type of aperture (see Galkin module
        :param anisotropy_model: stellar anisotropy model (see Galkin module)
        :param r_eff: a rough estimate of the half light radius of the lens light in case of computing the MGE of the
         light profile
        :param kwargs_numerics: keyword arguments that contain numerical options (see Galkin module)
        :param MGE_light: bool, if true performs the MGE for the light distribution
        :param MGE_mass: bool, if true performs the MGE for the mass distribution
        :param lens_model_kinematics_bool: bool list of length of the lens model. Only takes a subset of all the models
            as part of the kinematics computation (can be used to ignore substructure, shear etc that do not describe the
            main deflector potential
        :param light_model_kinematics_bool: bool list of length of the light model. Only takes a subset of all the models
            as part of the kinematics computation (can be used to ignore light components that do not describe the main
            deflector
        :param Hernquist_approx: bool, if True, uses a Hernquist light profile matched to the half light radius of the deflector light profile to compute the kinematics
        :param kappa_ext: external convergence not accounted in the lens models
        :return: LOS velocity dispersion [km/s]
        """

        kwargs_cosmo = {
            'D_d': self.lensCosmo.D_d,
            'D_s': self.lensCosmo.D_s,
            'D_ds': self.lensCosmo.D_ds
        }

        mass_profile_list, kwargs_profile, light_profile_list, kwargs_light = self.kinematic_profiles(
            kwargs_lens,
            kwargs_lens_light,
            r_eff=r_eff,
            MGE_light=MGE_light,
            MGE_mass=MGE_mass,
            lens_model_kinematics_bool=lens_model_kinematics_bool,
            light_model_kinematics_bool=light_model_kinematics_bool,
            Hernquist_approx=Hernquist_approx)
        galkin = Galkin(mass_profile_list,
                        light_profile_list,
                        aperture_type=aperture_type,
                        anisotropy_model=anisotropy_model,
                        fwhm=psf_fwhm,
                        psf_type=psf_type,
                        moffat_beta=moffat_beta,
                        kwargs_cosmo=kwargs_cosmo,
                        **kwargs_numerics)
        sigma = galkin.vel_disp(kwargs_profile, kwargs_light,
                                kwargs_anisotropy, kwargs_aperture)
        sigma *= np.sqrt(1 - kappa_ext)
        return sigma

    def kinematic_profiles(self,
                           kwargs_lens,
                           kwargs_lens_light,
                           r_eff,
                           MGE_light=False,
                           MGE_mass=False,
                           lens_model_kinematics_bool=None,
                           light_model_kinematics_bool=None,
                           Hernquist_approx=False):
        """
        translates the lenstronomy lens and mass profiles into a (sub) set of profiles that are compatible with the GalKin module to compute the kinematics thereof.

        :param kwargs_lens: lens model parameters
        :param kwargs_lens_light: lens light parameters
        :param r_eff: a rough estimate of the half light radius of the lens light in case of computing the MGE of the
         light profile
        :param MGE_light: bool, if true performs the MGE for the light distribution
        :param MGE_mass: bool, if true performs the MGE for the mass distribution
        :param lens_model_kinematics_bool: bool list of length of the lens model. Only takes a subset of all the models
            as part of the kinematics computation (can be used to ignore substructure, shear etc that do not describe the
            main deflector potential
        :param light_model_kinematics_bool: bool list of length of the light model. Only takes a subset of all the models
            as part of the kinematics computation (can be used to ignore light components that do not describe the main
            deflector
        :param Hernquist_approx: bool, if True, uses a Hernquist light profile matched to the half light radius of the deflector light profile to compute the kinematics
        :return: mass_profile_list, kwargs_profile, light_profile_list, kwargs_light
        """

        mass_profile_list = []
        kwargs_profile = []
        if lens_model_kinematics_bool is None:
            lens_model_kinematics_bool = [True] * len(kwargs_lens)
        for i, lens_model in enumerate(self.kwargs_options['lens_model_list']):
            if lens_model_kinematics_bool[i] is True:
                mass_profile_list.append(lens_model)
                if lens_model in ['INTERPOL', 'INTERPOL_SCLAED']:
                    center_x, center_y = self._lensModelExt.lens_center(
                        kwargs_lens, k=i)
                    kwargs_lens_i = copy.deepcopy(kwargs_lens[i])
                    kwargs_lens_i['grid_interp_x'] -= center_x
                    kwargs_lens_i['grid_interp_y'] -= center_y
                else:
                    kwargs_lens_i = {
                        k: v
                        for k, v in kwargs_lens[i].items()
                        if not k in ['center_x', 'center_y']
                    }
                kwargs_profile.append(kwargs_lens_i)

        if MGE_mass is True:
            lensModel = LensModel(lens_model_list=mass_profile_list)
            massModel = LensModelExtensions(lensModel)
            theta_E = massModel.effective_einstein_radius(kwargs_profile)
            r_array = np.logspace(-4, 2, 200) * theta_E
            mass_r = lensModel.kappa(r_array, np.zeros_like(r_array),
                                     kwargs_profile)
            amps, sigmas, norm = mge.mge_1d(r_array, mass_r, N=20)
            mass_profile_list = ['MULTI_GAUSSIAN_KAPPA']
            kwargs_profile = [{'amp': amps, 'sigma': sigmas}]

        light_profile_list = []
        kwargs_light = []
        if light_model_kinematics_bool is None:
            light_model_kinematics_bool = [True] * len(kwargs_lens_light)
        for i, light_model in enumerate(
                self.kwargs_options['lens_light_model_list']):
            if light_model_kinematics_bool[i]:
                light_profile_list.append(light_model)
                kwargs_lens_light_i = {
                    k: v
                    for k, v in kwargs_lens_light[i].items()
                    if not k in ['center_x', 'center_y']
                }
                if 'e1' in kwargs_lens_light_i:
                    kwargs_lens_light_i['e1'] = 0
                    kwargs_lens_light_i['e2'] = 0
                kwargs_light.append(kwargs_lens_light_i)
        if Hernquist_approx is True:
            light_profile_list = ['HERNQUIST']
            kwargs_light = [{'Rs': r_eff, 'amp': 1.}]
        else:
            if MGE_light is True:
                lightModel = LightModel(light_profile_list)
                r_array = np.logspace(-3, 2, 200) * r_eff * 2
                flux_r = lightModel.surface_brightness(r_array, 0,
                                                       kwargs_light)
                amps, sigmas, norm = mge.mge_1d(r_array, flux_r, N=20)
                light_profile_list = ['MULTI_GAUSSIAN']
                kwargs_light = [{'amp': amps, 'sigma': sigmas}]
        return mass_profile_list, kwargs_profile, light_profile_list, kwargs_light

    def angular_diameter_relations(self, sigma_v_model, sigma_v, kappa_ext,
                                   D_dt_model):
        """

        :return:
        """
        sigma_v2_model = sigma_v_model**2
        Ds_Dds = sigma_v**2 / (1 - kappa_ext) / (
            sigma_v2_model * self.lensCosmo.D_ds / self.lensCosmo.D_s)
        D_d = D_dt_model / (1 + self.lensCosmo.z_lens) / Ds_Dds / (1 -
                                                                   kappa_ext)
        return D_d, Ds_Dds

    def angular_distances(self, sigma_v_measured, time_delay_measured,
                          kappa_ext, sigma_v_modeled, fermat_pot):
        """

        :param sigma_v_measured: velocity dispersion measured [km/s]
        :param time_delay_measured: time delay measured [d]
        :param kappa_ext: external convergence estimated []
        :param sigma_v_modeled: lens model velocity dispersion with default cosmology and without external convergence [km/s]
        :param fermat_pot: fermat potential of lens model, modulo MSD of kappa_ext [arcsec^2]
        :return: D_d and D_d*D_s/D_ds, units in Mpc physical
        """

        Ds_Dds = (sigma_v_measured / float(sigma_v_modeled))**2 / (
            self.lensCosmo.D_ds / self.lensCosmo.D_s) / (1. - kappa_ext)
        DdDs_Dds = 1. / (1 + self.lensCosmo.z_lens) / (1. - kappa_ext) * (
            const.c * time_delay_measured *
            const.day_s) / (fermat_pot * const.arcsec**2) / const.Mpc
        return Ds_Dds, DdDs_Dds
示例#4
0
class LensModel(object):
    """
    class to handle an arbitrary list of lens models. This is the main lenstronomy LensModel API for all other modules.
    """
    def __init__(self,
                 lens_model_list,
                 z_lens=None,
                 z_source=None,
                 lens_redshift_list=None,
                 cosmo=None,
                 multi_plane=False,
                 numerical_alpha_class=None,
                 observed_convention_index=None,
                 z_source_convention=None,
                 cosmo_interp=False,
                 z_interp_stop=None,
                 num_z_interp=100):
        """

        :param lens_model_list: list of strings with lens model names
        :param z_lens: redshift of the deflector (only considered when operating in single plane mode).
        Is only needed for specific functions that require a cosmology.
        :param z_source: redshift of the source: Needed in multi_plane option only,
        not required for the core functionalities in the single plane mode.
        :param lens_redshift_list: list of deflector redshift (corresponding to the lens model list),
        only applicable in multi_plane mode.
        :param cosmo: instance of the astropy cosmology class. If not specified, uses the default cosmology.
        :param multi_plane: bool, if True, uses multi-plane mode. Default is False.
        :param numerical_alpha_class: an instance of a custom class for use in NumericalAlpha() lens model
        (see documentation in Profiles/numerical_alpha)
        :param observed_convention_index: a list of indices, corresponding to the lens_model_list element with same
        index, where the 'center_x' and 'center_y' kwargs correspond to observed (lensed) positions, not physical
        positions. The code will compute the physical locations when performing computations
        :param z_source_convention: float, redshift of a source to define the reduced deflection angles of the lens
        models. If None, 'z_source' is used.
        :param cosmo_interp: boolean (only employed in multi-plane mode), interpolates astropy.cosmology distances for
        faster calls when accessing several lensing planes
        :param z_interp_stop: (only in multi-plane with cosmo_interp=True); maximum redshift for distance interpolation
        This number should be higher or equal the maximum of the source redshift and/or the z_source_convention
        :param num_z_interp: (only in multi-plane with cosmo_interp=True); number of redshift bins for interpolating
        distances
        """
        self.lens_model_list = lens_model_list
        self.z_lens = z_lens
        self.z_source = z_source
        self._z_source_convention = z_source_convention
        self.redshift_list = lens_redshift_list

        if cosmo is None:
            from astropy.cosmology import default_cosmology
            cosmo = default_cosmology.get()
        self.cosmo = cosmo
        self.multi_plane = multi_plane
        if multi_plane is True:
            if z_source is None:
                raise ValueError(
                    'z_source needs to be set for multi-plane lens modelling.')

            self.lens_model = MultiPlane(
                z_source,
                lens_model_list,
                lens_redshift_list,
                cosmo=cosmo,
                numerical_alpha_class=numerical_alpha_class,
                observed_convention_index=observed_convention_index,
                z_source_convention=z_source_convention,
                cosmo_interp=cosmo_interp,
                z_interp_stop=z_interp_stop,
                num_z_interp=num_z_interp)
        else:
            self.lens_model = SinglePlane(
                lens_model_list,
                numerical_alpha_class=numerical_alpha_class,
                lens_redshift_list=lens_redshift_list,
                z_source_convention=z_source_convention)
        if z_lens is not None and z_source is not None:
            self._lensCosmo = LensCosmo(z_lens, z_source, cosmo=cosmo)

    def ray_shooting(self, x, y, kwargs, k=None):
        """
        maps image to source position (inverse deflection)

        :param x: x-position (preferentially arcsec)
        :type x: numpy array
        :param y: y-position (preferentially arcsec)
        :type y: numpy array
        :param kwargs: list of keyword arguments of lens model parameters matching the lens model classes
        :param k: only evaluate the k-th lens model
        :return: source plane positions corresponding to (x, y) in the image plane
        """
        return self.lens_model.ray_shooting(x, y, kwargs, k=k)

    def fermat_potential(self,
                         x_image,
                         y_image,
                         kwargs_lens,
                         x_source=None,
                         y_source=None):
        """
        fermat potential (negative sign means earlier arrival time)
        for Multi-plane lensing, it computes the effective Fermat potential (derived from the arrival time and
        subtracted off the time-delay distance for the given cosmology). The units are given in arcsecond square.

        :param x_image: image position
        :param y_image: image position
        :param x_source: source position
        :param y_source: source position
        :param kwargs_lens: list of keyword arguments of lens model parameters matching the lens model classes
        :return: fermat potential in arcsec**2 without geometry term (second part of Eqn 1 in Suyu et al. 2013) as a list
        """
        if hasattr(self.lens_model, 'fermat_potential'):
            return self.lens_model.fermat_potential(x_image, y_image,
                                                    kwargs_lens, x_source,
                                                    y_source)
        elif hasattr(self.lens_model, 'arrival_time') and hasattr(
                self, '_lensCosmo'):
            dt = self.lens_model.arrival_time(x_image, y_image, kwargs_lens)
            fermat_pot_eff = dt * const.c / self._lensCosmo.ddt / const.Mpc * const.day_s / const.arcsec**2
            return fermat_pot_eff
        else:
            raise ValueError(
                'In multi-plane lensing you need to provide a specific z_lens and z_source for which the '
                'effective Fermat potential is evaluated')

    def arrival_time(self, x_image, y_image, kwargs_lens, kappa_ext=0):
        """

        :param x_image: image position
        :param y_image: image position
        :param kwargs_lens: lens model parameter keyword argument list
        :param kappa_ext: external convergence contribution not accounted in the lens model that leads to the same
         observables in position and relative fluxes but rescales the time delays
        :return: arrival time of image positions in units of days
        """
        if hasattr(self.lens_model, 'arrival_time'):
            arrival_time = self.lens_model.arrival_time(
                x_image, y_image, kwargs_lens)
        else:
            fermat_pot = self.lens_model.fermat_potential(
                x_image, y_image, kwargs_lens)
            if not hasattr(self, '_lensCosmo'):
                raise ValueError(
                    "LensModel class was not initialized with lens and source redshifts!"
                )
            arrival_time = self._lensCosmo.time_delay_units(fermat_pot)
        arrival_time *= (1 - kappa_ext)
        return arrival_time

    def potential(self, x, y, kwargs, k=None):
        """
        lensing potential

        :param x: x-position (preferentially arcsec)
        :type x: numpy array
        :param y: y-position (preferentially arcsec)
        :type y: numpy array
        :param kwargs: list of keyword arguments of lens model parameters matching the lens model classes
        :param k: only evaluate the k-th lens model
        :return: lensing potential in units of arcsec^2
        """
        return self.lens_model.potential(x, y, kwargs, k=k)

    def alpha(self, x, y, kwargs, k=None, diff=None):
        """
        deflection angles

        :param x: x-position (preferentially arcsec)
        :type x: numpy array
        :param y: y-position (preferentially arcsec)
        :type y: numpy array
        :param kwargs: list of keyword arguments of lens model parameters matching the lens model classes
        :param k: only evaluate the k-th lens model
        :param diff: None or float. If set, computes the deflection as a finite numerical differential of the lensing
         potential. This differential is only applicable in the single lensing plane where the form of the lensing
         potential is analytically known
        :return: deflection angles in units of arcsec
        """
        if diff is None:
            return self.lens_model.alpha(x, y, kwargs, k=k)
        elif self.multi_plane is False:
            return self._deflection_differential(x, y, kwargs, k=k, diff=diff)
        else:
            raise ValueError(
                'numerical differentiation of lensing potential is not available in the multi-plane '
                'setting as analytical form of lensing potential is not available.'
            )

    def hessian(self, x, y, kwargs, k=None, diff=None, diff_method='square'):
        """
        hessian matrix

        :param x: x-position (preferentially arcsec)
        :type x: numpy array
        :param y: y-position (preferentially arcsec)
        :type y: numpy array
        :param kwargs: list of keyword arguments of lens model parameters matching the lens model classes
        :param k: only evaluate the k-th lens model
        :param diff: float, scale over which the finite numerical differential is computed. If None, then using the
         exact (if available) differentials.
        :param diff_method: string, 'square' or 'cross', indicating whether finite differentials are computed from a
         cross or a square of points around (x, y)
        :return: f_xx, f_xy, f_yx, f_yy components
        """
        if diff is None:
            return self.lens_model.hessian(x, y, kwargs, k=k)
        elif diff_method == 'square':
            return self._hessian_differential_square(x,
                                                     y,
                                                     kwargs,
                                                     k=k,
                                                     diff=diff)
        elif diff_method == 'cross':
            return self._hessian_differential_cross(x,
                                                    y,
                                                    kwargs,
                                                    k=k,
                                                    diff=diff)
        else:
            raise ValueError(
                'diff_method %s not supported. Chose among "square" or "cross".'
                % diff_method)

    def kappa(self, x, y, kwargs, k=None, diff=None, diff_method='square'):
        """
        lensing convergence k = 1/2 laplacian(phi)

        :param x: x-position (preferentially arcsec)
        :type x: numpy array
        :param y: y-position (preferentially arcsec)
        :type y: numpy array
        :param kwargs: list of keyword arguments of lens model parameters matching the lens model classes
        :param k: only evaluate the k-th lens model
        :param diff: float, scale over which the finite numerical differential is computed. If None, then using the
         exact (if available) differentials.
        :param diff_method: string, 'square' or 'cross', indicating whether finite differentials are computed from a
         cross or a square of points around (x, y)
        :return: lensing convergence
        """

        f_xx, f_xy, f_yx, f_yy = self.hessian(x,
                                              y,
                                              kwargs,
                                              k=k,
                                              diff=diff,
                                              diff_method=diff_method)
        kappa = 1. / 2 * (f_xx + f_yy)
        return kappa

    def curl(self, x, y, kwargs, k=None, diff=None, diff_method='square'):
        """
        curl computation F_xy - F_yx

        :param x: x-position (preferentially arcsec)
        :type x: numpy array
        :param y: y-position (preferentially arcsec)
        :type y: numpy array
        :param kwargs: list of keyword arguments of lens model parameters matching the lens model classes
        :param k: only evaluate the k-th lens model
        :param diff: float, scale over which the finite numerical differential is computed. If None, then using the
         exact (if available) differentials.
        :param diff_method: string, 'square' or 'cross', indicating whether finite differentials are computed from a
         cross or a square of points around (x, y)
        :return: curl at position (x, y)
        """
        f_xx, f_xy, f_yx, f_yy = self.hessian(x,
                                              y,
                                              kwargs,
                                              k=k,
                                              diff=diff,
                                              diff_method=diff_method)
        return f_xy - f_yx

    def gamma(self, x, y, kwargs, k=None, diff=None, diff_method='square'):
        """
        shear computation
        g1 = 1/2(d^2phi/dx^2 - d^2phi/dy^2)
        g2 = d^2phi/dxdy

        :param x: x-position (preferentially arcsec)
        :type x: numpy array
        :param y: y-position (preferentially arcsec)
        :type y: numpy array
        :param kwargs: list of keyword arguments of lens model parameters matching the lens model classes
        :param k: only evaluate the k-th lens model
        :param diff: float, scale over which the finite numerical differential is computed. If None, then using the
         exact (if available) differentials.
        :param diff_method: string, 'square' or 'cross', indicating whether finite differentials are computed from a
         cross or a square of points around (x, y)
        :return: gamma1, gamma2
        """

        f_xx, f_xy, f_yx, f_yy = self.hessian(x,
                                              y,
                                              kwargs,
                                              k=k,
                                              diff=diff,
                                              diff_method=diff_method)
        gamma1 = 1. / 2 * (f_xx - f_yy)
        gamma2 = f_xy
        return gamma1, gamma2

    def magnification(self,
                      x,
                      y,
                      kwargs,
                      k=None,
                      diff=None,
                      diff_method='square'):
        """
        magnification
        mag = 1/det(A)
        A = 1 - d^2phi/d_ij

        :param x: x-position (preferentially arcsec)
        :type x: numpy array
        :param y: y-position (preferentially arcsec)
        :type y: numpy array
        :param kwargs: list of keyword arguments of lens model parameters matching the lens model classes
        :param k: only evaluate the k-th lens model
        :param diff: float, scale over which the finite numerical differential is computed. If None, then using the
         exact (if available) differentials.
        :param diff_method: string, 'square' or 'cross', indicating whether finite differentials are computed from a
         cross or a square of points around (x, y)
        :return: magnification
        """

        f_xx, f_xy, f_yx, f_yy = self.hessian(x,
                                              y,
                                              kwargs,
                                              k=k,
                                              diff=diff,
                                              diff_method=diff_method)
        det_A = (1 - f_xx) * (1 - f_yy) - f_xy * f_yx
        return 1. / det_A  # attention, if dividing by zero

    def flexion(self, x, y, kwargs, k=None, diff=0.000001, hessian_diff=True):
        """
        third derivatives (flexion)

        :param x: x-position (preferentially arcsec)
        :type x: numpy array
        :param y: y-position (preferentially arcsec)
        :type y: numpy array
        :param kwargs: list of keyword arguments of lens model parameters matching the lens model classes
        :param k: int or None, if set, only evaluates the differential from one model component
        :param diff: numerical differential length of Flexion
        :param hessian_diff: boolean, if true also computes the numerical differential length of Hessian (optional)
        :return: f_xxx, f_xxy, f_xyy, f_yyy
        """
        if hessian_diff is not True:
            hessian_diff = None
        f_xx_dx, f_xy_dx, f_yx_dx, f_yy_dx = self.hessian(x + diff / 2,
                                                          y,
                                                          kwargs,
                                                          k=k,
                                                          diff=hessian_diff)
        f_xx_dy, f_xy_dy, f_yx_dy, f_yy_dy = self.hessian(x,
                                                          y + diff / 2,
                                                          kwargs,
                                                          k=k,
                                                          diff=hessian_diff)

        f_xx_dx_, f_xy_dx_, f_yx_dx_, f_yy_dx_ = self.hessian(
            x - diff / 2, y, kwargs, k=k, diff=hessian_diff)
        f_xx_dy_, f_xy_dy_, f_yx_dy_, f_yy_dy_ = self.hessian(
            x, y - diff / 2, kwargs, k=k, diff=hessian_diff)

        f_xxx = (f_xx_dx - f_xx_dx_) / diff
        f_xxy = (f_xx_dy - f_xx_dy_) / diff
        f_xyy = (f_xy_dy - f_xy_dy_) / diff
        f_yyy = (f_yy_dy - f_yy_dy_) / diff
        return f_xxx, f_xxy, f_xyy, f_yyy

    def set_static(self, kwargs):
        """
        set this instance to a static lens model. This can improve the speed in evaluating lensing quantities at
        different positions but must not be used with different lens model parameters!

        :param kwargs: lens model keyword argument list
        :return: kwargs_updated (in case of image position convention in multiplane lensing this is changed)
        """
        return self.lens_model.set_static(kwargs)

    def set_dynamic(self):
        """
        deletes cache for static setting and makes sure the observed convention in the position of lensing profiles in
        the multi-plane setting is enabled. Dynamic is the default setting of this class enabling an accurate computation
        of lensing quantities with different parameters in the lensing profiles.

        :return: None
        """
        self.lens_model.set_dynamic()

    def _deflection_differential(self, x, y, kwargs, k=None, diff=0.00001):
        """

        :param x: x-coordinate
        :param y: y-coordinate
        :param kwargs: keyword argument list
        :param k: int or None, if set, only evaluates the differential from one model component
        :param diff: finite differential length
        :return: f_x, f_y
        """
        phi_dx = self.lens_model.potential(x + diff / 2, y, kwargs=kwargs, k=k)
        phi_dy = self.lens_model.potential(x, y + diff / 2, kwargs=kwargs, k=k)
        phi_dx_ = self.lens_model.potential(x - diff / 2,
                                            y,
                                            kwargs=kwargs,
                                            k=k)
        phi_dy_ = self.lens_model.potential(x,
                                            y - diff / 2,
                                            kwargs=kwargs,
                                            k=k)
        f_x = (phi_dx - phi_dx_) / diff
        f_y = (phi_dy - phi_dy_) / diff
        return f_x, f_y

    def _hessian_differential_cross(self, x, y, kwargs, k=None, diff=0.00001):
        """
        computes the numerical differentials over a finite range for f_xx, f_yy, f_xy from f_x and f_y
        The differentials are computed along the cross centered at (x, y).

        :param x: x-coordinate
        :param y: y-coordinate
        :param kwargs: lens model keyword argument list
        :param k: int, list of bools or None, indicating a subset of lens models to be evaluated
        :param diff: float, scale of the finite differential (diff/2 in each direction used to compute the differential
        :return: f_xx, f_xy, f_yx, f_yy
        """
        alpha_ra_dx, alpha_dec_dx = self.alpha(x + diff / 2, y, kwargs, k=k)
        alpha_ra_dy, alpha_dec_dy = self.alpha(x, y + diff / 2, kwargs, k=k)

        alpha_ra_dx_, alpha_dec_dx_ = self.alpha(x - diff / 2, y, kwargs, k=k)
        alpha_ra_dy_, alpha_dec_dy_ = self.alpha(x, y - diff / 2, kwargs, k=k)

        dalpha_rara = (alpha_ra_dx - alpha_ra_dx_) / diff
        dalpha_radec = (alpha_ra_dy - alpha_ra_dy_) / diff
        dalpha_decra = (alpha_dec_dx - alpha_dec_dx_) / diff
        dalpha_decdec = (alpha_dec_dy - alpha_dec_dy_) / diff

        f_xx = dalpha_rara
        f_yy = dalpha_decdec
        f_xy = dalpha_radec
        f_yx = dalpha_decra
        return f_xx, f_xy, f_yx, f_yy

    def _hessian_differential_square(self, x, y, kwargs, k=None, diff=0.00001):
        """
        computes the numerical differentials over a finite range for f_xx, f_yy, f_xy from f_x and f_y
        The differentials are computed on the square around (x, y). This minimizes curl.

        :param x: x-coordinate
        :param y: y-coordinate
        :param kwargs: lens model keyword argument list
        :param k: int, list of booleans or None, indicating a subset of lens models to be evaluated
        :param diff: float, scale of the finite differential (diff/2 in each direction used to compute the differential
        :return: f_xx, f_xy, f_yx, f_yy
        """
        alpha_ra_pp, alpha_dec_pp = self.alpha(x + diff / 2,
                                               y + diff / 2,
                                               kwargs,
                                               k=k)
        alpha_ra_pn, alpha_dec_pn = self.alpha(x + diff / 2,
                                               y - diff / 2,
                                               kwargs,
                                               k=k)

        alpha_ra_np, alpha_dec_np = self.alpha(x - diff / 2,
                                               y + diff / 2,
                                               kwargs,
                                               k=k)
        alpha_ra_nn, alpha_dec_nn = self.alpha(x - diff / 2,
                                               y - diff / 2,
                                               kwargs,
                                               k=k)

        f_xx = (alpha_ra_pp - alpha_ra_np + alpha_ra_pn -
                alpha_ra_nn) / diff / 2
        f_xy = (alpha_ra_pp - alpha_ra_pn + alpha_ra_np -
                alpha_ra_nn) / diff / 2
        f_yx = (alpha_dec_pp - alpha_dec_np + alpha_dec_pn -
                alpha_dec_nn) / diff / 2
        f_yy = (alpha_dec_pp - alpha_dec_pn + alpha_dec_np -
                alpha_dec_nn) / diff / 2

        return f_xx, f_xy, f_yx, f_yy
示例#5
0
class LensProp(object):
    """
    this class contains routines to compute time delays, magnification ratios, line of sight velocity dispersions etc for a given lens model
    """
    def __init__(self, z_lens, z_source, kwargs_model, cosmo=None):
        self.z_d = z_lens
        self.z_s = z_source
        self.lensCosmo = LensCosmo(z_lens, z_source, cosmo=cosmo)
        self.lens_analysis = LensAnalysis(kwargs_model)
        self.lens_model = LensModelExtensions(
            lens_model_list=kwargs_model['lens_model_list'])
        self.kwargs_options = kwargs_model
        kwargs_cosmo = {
            'D_d': self.lensCosmo.D_d,
            'D_s': self.lensCosmo.D_s,
            'D_ds': self.lensCosmo.D_ds
        }
        self.dispersion = Velocity_dispersion(kwargs_cosmo=kwargs_cosmo)

    def time_delays(self, kwargs_lens, kwargs_ps, kappa_ext=0):
        fermat_pot = self.lens_analysis.fermat_potential(
            kwargs_lens, kwargs_ps)
        time_delay = self.lensCosmo.time_delay_units(fermat_pot, kappa_ext)
        return time_delay

    def velocity_dispersion(self,
                            kwargs_lens,
                            kwargs_lens_light,
                            aniso_param=1,
                            r_eff=None,
                            R_slit=0.81,
                            dR_slit=0.1,
                            psf_fwhm=0.7,
                            num_evaluate=100):
        gamma = kwargs_lens[0]['gamma']
        if r_eff is None:
            r_eff = self.lens_analysis.half_light_radius_lens(
                kwargs_lens_light)
        theta_E = kwargs_lens[0]['theta_E']
        if self.dispersion.beta_const is False:
            aniso_param *= r_eff
        sigma2 = self.dispersion.vel_disp(gamma,
                                          theta_E,
                                          r_eff,
                                          aniso_param,
                                          R_slit,
                                          dR_slit,
                                          FWHM=psf_fwhm,
                                          num=num_evaluate)
        return sigma2

    def velocity_disperson_numerical(self,
                                     kwargs_lens,
                                     kwargs_lens_light,
                                     kwargs_anisotropy,
                                     kwargs_aperture,
                                     psf_fwhm,
                                     aperture_type,
                                     anisotropy_model,
                                     r_eff=1.,
                                     kwargs_numerics={},
                                     MGE_light=False,
                                     MGE_mass=False):
        """

        :param kwargs_lens:
        :param kwargs_lens_light:
        :param kwargs_anisotropy:
        :param kwargs_aperature:
        :return:
        """
        kwargs_cosmo = {
            'D_d': self.lensCosmo.D_d,
            'D_s': self.lensCosmo.D_s,
            'D_ds': self.lensCosmo.D_ds
        }
        mass_profile_list = []
        kwargs_profile = []
        lens_model_internal_bool = self.kwargs_options.get(
            'lens_model_deflector_bool', [True] * len(kwargs_lens))
        for i, lens_model in enumerate(self.kwargs_options['lens_model_list']):
            if lens_model_internal_bool[i]:
                mass_profile_list.append(lens_model)
                kwargs_lens_i = {
                    k: v
                    for k, v in kwargs_lens[i].items()
                    if not k in ['center_x', 'center_y']
                }
                kwargs_profile.append(kwargs_lens_i)

        if MGE_mass is True:
            massModel = LensModelExtensions(lens_model_list=mass_profile_list)
            theta_E = massModel.effective_einstein_radius(kwargs_lens)
            r_array = np.logspace(-4, 2, 200) * theta_E
            mass_r = massModel.kappa(r_array, 0, kwargs_profile)
            amps, sigmas, norm = mge.mge_1d(r_array, mass_r, N=20)
            mass_profile_list = ['MULTI_GAUSSIAN_KAPPA']
            kwargs_profile = [{'amp': amps, 'sigma': sigmas}]

        light_profile_list = []
        kwargs_light = []
        lens_light_model_internal_bool = self.kwargs_options.get(
            'light_model_deflector_bool', [True] * len(kwargs_lens_light))
        for i, light_model in enumerate(
                self.kwargs_options['lens_light_model_list']):
            if lens_light_model_internal_bool[i]:
                light_profile_list.append(light_model)
                kwargs_Lens_light_i = {
                    k: v
                    for k, v in kwargs_lens_light[i].items()
                    if not k in ['center_x', 'center_y']
                }
                if 'q' in kwargs_Lens_light_i:
                    kwargs_Lens_light_i['q'] = 1
                kwargs_light.append(kwargs_Lens_light_i)

        if MGE_light is True:
            lightModel = LightModel(light_profile_list)
            r_array = np.logspace(-3, 2, 200) * r_eff * 2
            flux_r = lightModel.surface_brightness(r_array, 0, kwargs_light)
            amps, sigmas, norm = mge.mge_1d(r_array, flux_r, N=20)
            light_profile_list = ['MULTI_GAUSSIAN']
            kwargs_light = [{'amp': amps, 'sigma': sigmas}]

        galkin = Galkin(mass_profile_list,
                        light_profile_list,
                        aperture_type=aperture_type,
                        anisotropy_model=anisotropy_model,
                        fwhm=psf_fwhm,
                        kwargs_cosmo=kwargs_cosmo,
                        kwargs_numerics=kwargs_numerics)
        sigma_v = galkin.vel_disp(kwargs_profile,
                                  kwargs_light,
                                  kwargs_anisotropy,
                                  kwargs_aperture,
                                  r_eff=r_eff)
        return sigma_v

    def angular_diameter_relations(self, sigma_v_model, sigma_v, kappa_ext,
                                   D_dt_model, z_d):
        """

        :return:
        """
        sigma_v2_model = sigma_v_model**2
        Ds_Dds = sigma_v**2 / (1 - kappa_ext) / (
            sigma_v2_model * self.lensCosmo.D_ds / self.lensCosmo.D_s)
        D_d = D_dt_model / (1 + z_d) / Ds_Dds / (1 - kappa_ext)
        return D_d, Ds_Dds

    def angular_distances(self, sigma_v_measured, time_delay_measured,
                          kappa_ext, sigma_v_modeled, fermat_pot):
        """

        :param sigma_v_measured: velocity dispersion measured [km/s]
        :param time_delay_measured: time delay measured [d]
        :param kappa_ext: external convergence estimated []
        :param sigma_v_modeled: lens model velocity dispersion with default cosmology and without external convergence [km/s]
        :param fermat_pot: fermat potential of lens model, modulo MSD of kappa_ext [arcsec^2]
        :return: D_d and D_d*D_s/D_ds, units in Mpc physical
        """

        Ds_Dds = (sigma_v_measured / sigma_v_modeled)**2 / (
            self.lensCosmo.D_ds / self.lensCosmo.D_s) / (1 - kappa_ext)
        DdDs_Dds = 1. / (1 + self.lensCosmo.z_lens) / (1 - kappa_ext) * (
            const.c * time_delay_measured *
            const.day_s) / (fermat_pot * const.arcsec**2) / const.Mpc
        return Ds_Dds, DdDs_Dds
示例#6
0
class TDCosmography(KinematicsAPI):
    """
    class equipped to perform a cosmographic analysis from a lens model with added measurements of time delays and
    kinematics.
    This class does not require any cosmological knowledge and can return angular diameter distance estimates
    self-consistently integrating the kinematics routines and time delay estimates in the lens modeling.
    This description follows Birrer et al. 2016, 2019.


    """
    def __init__(self,
                 z_lens,
                 z_source,
                 kwargs_model,
                 cosmo_fiducial=None,
                 lens_model_kinematics_bool=None,
                 light_model_kinematics_bool=None,
                 kwargs_seeing={},
                 kwargs_aperture={},
                 anisotropy_model=None):

        if cosmo_fiducial is None:
            cosmo_fiducial = default_cosmology.get()
        self._z_lens = z_lens
        self._z_source = z_source
        self._cosmo_fiducial = cosmo_fiducial
        self._lens_cosmo = LensCosmo(z_lens=z_lens,
                                     z_source=z_source,
                                     cosmo=self._cosmo_fiducial)
        self.LensModel, self.SourceModel, self.LensLightModel, self.PointSource, extinction_class = class_creator.create_class_instances(
            all_models=True, **kwargs_model)
        super(TDCosmography, self).__init__(
            z_lens=z_lens,
            z_source=z_source,
            kwargs_model=kwargs_model,
            cosmo=cosmo_fiducial,
            lens_model_kinematics_bool=lens_model_kinematics_bool,
            light_model_kinematics_bool=light_model_kinematics_bool,
            kwargs_seeing=kwargs_seeing,
            kwargs_aperture=kwargs_aperture,
            anisotropy_model=anisotropy_model)

    def time_delays(self, kwargs_lens, kwargs_ps, kappa_ext=0):
        """
        predicts the time delays of the image positions given the fiducial cosmology

        :param kwargs_lens: lens model parameters
        :param kwargs_ps: point source parameters
        :param kappa_ext: external convergence (optional)
        :return: time delays at image positions for the fixed cosmology
        """
        fermat_pot = self.fermat_potential(kwargs_lens, kwargs_ps)
        time_delay = self._lens_cosmo.time_delay_units(fermat_pot, kappa_ext)
        return time_delay

    def fermat_potential(self, kwargs_lens, kwargs_ps):
        """

        :param kwargs_lens: lens model keyword argument list
        :param kwargs_ps: point source keyword argument list
        :return: Fermat potential of all the image positions in the first point source list entry
        """
        ra_pos, dec_pos = self.PointSource.image_position(
            kwargs_ps, kwargs_lens)
        ra_pos = ra_pos[0]
        dec_pos = dec_pos[0]
        ra_source, dec_source = self.LensModel.ray_shooting(
            ra_pos, dec_pos, kwargs_lens)
        sigma_source = np.sqrt(np.var(ra_source) + np.var(dec_source))
        if sigma_source > 0.001:
            Warning(
                'Source position computed from the different image positions do not trace back to the same position! '
                'The error is %s mas and may be larger than what is required for an accurate relative time delay estimate!'
                'See e.g. Birrer & Treu 2019.' % sigma_source * 1000)
        ra_source = np.mean(ra_source)
        dec_source = np.mean(dec_source)
        fermat_pot = self.LensModel.fermat_potential(ra_pos, dec_pos,
                                                     kwargs_lens, ra_source,
                                                     dec_source)
        return fermat_pot

    def velocity_dispersion_dimension_less(self,
                                           kwargs_lens,
                                           kwargs_lens_light,
                                           kwargs_anisotropy,
                                           r_eff=None,
                                           theta_E=None,
                                           gamma=None):
        """
        sigma**2 = Dd/Dds * c**2 * J(kwargs_lens, kwargs_light, anisotropy)
        (Equation 4.11 in Birrer et al. 2016 or Equation 6 in Birrer et al. 2019) J() is a dimensionless and
        cosmological independent quantity only depending on angular units. This function returns J given the lens
        and light parameters and the anisotropy choice without an external mass sheet correction.

        :param kwargs_lens: lens model keyword arguments
        :param kwargs_lens_light: lens light model keyword arguments
        :param kwargs_anisotropy: stellar anisotropy keyword arguments
        :param r_eff: projected half-light radius of the stellar light associated with the deflector galaxy, optional,
         if set to None will be computed in this function with default settings that may not be accurate.
        :return: dimensionless velocity dispersion (see e.g. Birrer et al. 2016, 2019)
        """
        sigma_v = self.velocity_dispersion(kwargs_lens=kwargs_lens,
                                           kwargs_lens_light=kwargs_lens_light,
                                           kwargs_anisotropy=kwargs_anisotropy,
                                           r_eff=r_eff,
                                           theta_E=theta_E,
                                           gamma=gamma)
        sigma_v *= 1000  # convert from [km/s] to  [m/s]
        J = sigma_v**2 * self._lens_cosmo.dds / self._lens_cosmo.ds / const.c**2
        return J

    def velocity_dispersion_map_dimension_less(self,
                                               kwargs_lens,
                                               kwargs_lens_light,
                                               kwargs_anisotropy,
                                               r_eff=None,
                                               theta_E=None,
                                               gamma=None):
        """
        sigma**2 = Dd/Dds * c**2 * J(kwargs_lens, kwargs_light, anisotropy)
        (Equation 4.11 in Birrer et al. 2016 or Equation 6 in Birrer et al. 2019) J() is a dimensionless and
        cosmological independent quantity only depending on angular units. This function returns J given the lens
        and light parameters and the anisotropy choice without an external mass sheet correction.
        This routine computes the IFU map of the kinematic quantities.

        :param kwargs_lens: lens model keyword arguments
        :param kwargs_lens_light: lens light model keyword arguments
        :param kwargs_anisotropy: stellar anisotropy keyword arguments
        :param r_eff: projected half-light radius of the stellar light associated with the deflector galaxy, optional,
         if set to None will be computed in this function with default settings that may not be accurate.
        :return: dimensionless velocity dispersion (see e.g. Birrer et al. 2016, 2019)
        """
        sigma_v_map = self.velocity_dispersion_map(
            kwargs_lens=kwargs_lens,
            kwargs_lens_light=kwargs_lens_light,
            kwargs_anisotropy=kwargs_anisotropy,
            r_eff=r_eff,
            theta_E=theta_E,
            gamma=gamma)
        sigma_v_map *= 1000  # convert from [km/s] to  [m/s]
        J_map = sigma_v_map**2 * self._lens_cosmo.dds / self._lens_cosmo.ds / const.c**2
        return J_map

    @staticmethod
    def ddt_from_time_delay(d_fermat_model,
                            dt_measured,
                            kappa_s=0,
                            kappa_ds=0,
                            kappa_d=0):
        """
        Time-delay distance in units of Mpc from the modeled Fermat potential and measured time delay from an image pair.

        :param d_fermat_model: relative Fermat potential between two images from the same source in units arcsec^2
        :param dt_measured: measured time delay between the same image pair in units of days
        :return: D_dt, time-delay distance
        """
        D_dt_model = dt_measured * const.day_s * const.c / const.Mpc / d_fermat_model / const.arcsec**2
        D_dt = D_dt_model * (1 - kappa_ds) / (1 - kappa_s) / (1 - kappa_d)
        return D_dt

    @staticmethod
    def ds_dds_from_kinematics(sigma_v, J, kappa_s=0, kappa_ds=0):
        """
        computes the estimate of the ratio of angular diameter distances Ds/Dds from the kinematic estimate of the lens
        and the measured dispersion.

        :param sigma_v: velocity dispersion [km/s]
        :param J: dimensionless kinematic constraint (see Birrer et al. 2016, 2019)
        :return: Ds/Dds
        """
        ds_dds_model = (sigma_v * 1000)**2 / const.c**2 / J
        ds_dds = ds_dds_model * (1 - kappa_ds) / (1 - kappa_s)
        return ds_dds

    def ddt_dd_from_time_delay_and_kinematics(self,
                                              d_fermat_model,
                                              dt_measured,
                                              sigma_v_measured,
                                              J,
                                              kappa_s=0,
                                              kappa_ds=0,
                                              kappa_d=0):
        """

        :param d_fermat_model: relative Fermat potential in units arcsec^2
        :param dt_measured: measured relative time delay [days]
        :param sigma_v_measured: 1-sigma Gaussian uncertainty in the measured velocity dispersion
        :param J: modeled dimensionless kinematic estimate
        :param kappa_s: LOS convergence from observer to source
        :param kappa_ds: LOS convergence from deflector to source
        :param kappa_d: LOS convergence from observer to deflector
        :return: D_dt, D_d
        """
        ddt = self.ddt_from_time_delay(d_fermat_model,
                                       dt_measured,
                                       kappa_s=kappa_s,
                                       kappa_ds=kappa_ds,
                                       kappa_d=kappa_d)
        ds_dds = self.ds_dds_from_kinematics(sigma_v_measured,
                                             J,
                                             kappa_s=kappa_s,
                                             kappa_ds=kappa_ds)
        dd = ddt / ds_dds / (1 + self._z_lens)
        return ddt, dd
示例#7
0
    def test_foreground_shear(self):
        """
        scenario: a shear field in the foreground of the main deflector is placed
        we compute the expected shear on the lens plain and effectively model the same system in a single plane
        configuration
        We check for consistency of the two approaches and whether the specific redshift of the foreground shear field has
        an impact on the arrival time surface
        :return:
        """
        z_source = 1.5
        z_lens = 0.5
        z_shear = 0.2
        x, y = np.array([1., 0.]), np.array([0., 2.])
        from astropy.cosmology import default_cosmology
        from lenstronomy.Cosmo.background import Background

        cosmo = default_cosmology.get()
        cosmo_bkg = Background(cosmo)
        e1, e2 = 0.01, 0.01  # shear terms caused by z_shear on z_source
        lens_model_list = ['SIS', 'SHEAR']
        redshift_list = [z_lens, z_shear]
        lensModelMutli = MultiPlane(z_source=z_source,
                                    lens_model_list=lens_model_list,
                                    lens_redshift_list=redshift_list)
        kwargs_lens_multi = [{
            'theta_E': 1,
            'center_x': 0,
            'center_y': 0
        }, {
            'e1': e1,
            'e2': e2
        }]
        alpha_x_multi, alpha_y_multi = lensModelMutli.alpha(
            x, y, kwargs_lens_multi)
        t_multi = lensModelMutli.arrival_time(x, y, kwargs_lens_multi)
        dt_multi = t_multi[0] - t_multi[1]
        physical_shear = cosmo_bkg.D_xy(0, z_source) / cosmo_bkg.D_xy(
            z_shear, z_source)
        foreground_factor = cosmo_bkg.D_xy(z_shear, z_lens) / cosmo_bkg.D_xy(
            0, z_lens) * physical_shear
        print(foreground_factor)
        lens_model_simple_list = ['SIS', 'FOREGROUND_SHEAR', 'SHEAR']
        kwargs_lens_single = [{
            'theta_E': 1,
            'center_x': 0,
            'center_y': 0
        }, {
            'e1': e1 * foreground_factor,
            'e2': e2 * foreground_factor
        }, {
            'e1': e1,
            'e2': e2
        }]
        lensModel = LensModel(lens_model_list=lens_model_simple_list)
        alpha_x_simple, alpha_y_simple = lensModel.alpha(
            x, y, kwargs_lens_single)
        npt.assert_almost_equal(alpha_x_simple, alpha_x_multi, decimal=8)
        npt.assert_almost_equal(alpha_y_simple, alpha_y_multi, decimal=8)

        ra_source, dec_source = lensModel.ray_shooting(x, y,
                                                       kwargs_lens_single)
        ra_source_multi, dec_source_multi = lensModelMutli.ray_shooting(
            x, y, kwargs_lens_multi)
        npt.assert_almost_equal(ra_source, ra_source_multi, decimal=8)
        npt.assert_almost_equal(dec_source, dec_source_multi, decimal=8)

        fermat_pot = lensModel.fermat_potential(x, y, ra_source, dec_source,
                                                kwargs_lens_single)
        from lenstronomy.Cosmo.lens_cosmo import LensCosmo
        lensCosmo = LensCosmo(z_lens, z_source, cosmo=cosmo)
        Dt = lensCosmo.D_dt
        print(lensCosmo.D_dt)
        #t_simple = const.delay_arcsec2days(fermat_pot, Dt)
        t_simple = lensCosmo.time_delay_units(fermat_pot)
        dt_simple = t_simple[0] - t_simple[1]
        print(t_simple, t_multi)
        npt.assert_almost_equal(dt_simple / dt_multi, 1, decimal=2)