예제 #1
0
class TestLensModel(object):
    """
    tests the source model routines
    """
    def setup(self):
        self.lensModel = SinglePlane(['GAUSSIAN'])
        self.kwargs = [{
            'amp': 1.,
            'sigma_x': 2.,
            'sigma_y': 2.,
            'center_x': 0.,
            'center_y': 0.
        }]

    def test_potential(self):
        output = self.lensModel.potential(x=1., y=1., kwargs=self.kwargs)
        assert output == 0.77880078307140488 / (8 * np.pi)

    def test_alpha(self):
        output1, output2 = self.lensModel.alpha(x=1., y=1., kwargs=self.kwargs)
        assert output1 == -0.19470019576785122 / (8 * np.pi)
        assert output2 == -0.19470019576785122 / (8 * np.pi)

    def test_ray_shooting(self):
        delta_x, delta_y = self.lensModel.ray_shooting(x=1.,
                                                       y=1.,
                                                       kwargs=self.kwargs)
        assert delta_x == 1 + 0.19470019576785122 / (8 * np.pi)
        assert delta_y == 1 + 0.19470019576785122 / (8 * np.pi)

    def test_mass_2d(self):
        lensModel = SinglePlane(['GAUSSIAN_KAPPA'])
        output = lensModel.mass_2d(r=1, kwargs=self.kwargs)
        assert output == 0.11750309741540453
예제 #2
0
    def __init__(self,
                 kwargs_model,
                 kwargs_cosmo,
                 interpol_grid_num=100,
                 log_integration=False,
                 max_integrate=100,
                 min_integrate=0.001):
        """

        :param interpol_grid_num:
        :param log_integration:
        :param max_integrate:
        :param min_integrate:
        """
        mass_profile_list = kwargs_model.get('mass_profile_list')
        light_profile_list = kwargs_model.get('light_profile_list')
        anisotropy_model = kwargs_model.get('anisotropy_model')
        self._interp_grid_num = interpol_grid_num
        self._log_int = log_integration
        self._max_integrate = max_integrate  # maximal integration (and interpolation) in units of arcsecs
        self._min_integrate = min_integrate  # min integration (and interpolation) in units of arcsecs
        self._max_interpolate = max_integrate  # we chose to set the interpolation range to the integration range
        self._min_interpolate = min_integrate  # we chose to set the interpolation range to the integration range
        self.lightProfile = LightProfile(light_profile_list,
                                         interpol_grid_num=interpol_grid_num,
                                         max_interpolate=max_integrate,
                                         min_interpolate=min_integrate)
        Anisotropy.__init__(self, anisotropy_type=anisotropy_model)
        self.cosmo = Cosmo(**kwargs_cosmo)
        self._mass_profile = SinglePlane(mass_profile_list)
예제 #3
0
    def __init__(self,
                 lens_model_list,
                 lens_redshift_list,
                 z_source_convention,
                 cosmo=None,
                 numerical_alpha_class=None):
        """

        :param lens_model_list: list of lens model strings
        :param lens_redshift_list: list of floats with redshifts of the lens models indicated in lens_model_list
        :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: instance of astropy.cosmology
        :param numerical_alpha_class: an instance of a custom class for use in NumericalAlpha() lens model
        (see documentation in Profiles/numerical_alpha)

        """
        self._cosmo_bkg = Background(cosmo)
        self._z_source_convention = z_source_convention
        if len(lens_redshift_list) > 0:
            z_lens_max = np.max(lens_redshift_list)
            if z_lens_max >= z_source_convention:
                raise ValueError(
                    'deflector redshifts higher or equal the source redshift convention (%s >= %s for the reduced lens'
                    ' model quantities not allowed (leads to negative reduced deflection angles!'
                    % (z_lens_max, z_source_convention))
        if not len(lens_model_list) == len(lens_redshift_list):
            raise ValueError(
                "The length of lens_model_list does not correspond to redshift_list"
            )

        self._lens_model_list = lens_model_list
        self._lens_redshift_list = lens_redshift_list
        self._lens_model = SinglePlane(
            lens_model_list, numerical_alpha_class=numerical_alpha_class)

        if len(lens_model_list) < 1:
            self._sorted_redshift_index = []
        else:
            self._sorted_redshift_index = self._index_ordering(
                lens_redshift_list)
        z_before = 0
        T_z = 0
        self._T_ij_list = []
        self._T_z_list = []
        self._reduced2physical_factor = []
        for idex in self._sorted_redshift_index:
            z_lens = self._lens_redshift_list[idex]
            if z_before == z_lens:
                delta_T = 0
            else:
                T_z = self._cosmo_bkg.T_xy(0, z_lens)
                delta_T = self._cosmo_bkg.T_xy(z_before, z_lens)
            self._T_ij_list.append(delta_T)
            self._T_z_list.append(T_z)
            factor = self._cosmo_bkg.D_xy(
                0, z_source_convention) / self._cosmo_bkg.D_xy(
                    z_lens, z_source_convention)
            self._reduced2physical_factor.append(factor)
            z_before = z_lens
예제 #4
0
 def test_density(self):
     theta_E = 1
     r = 1
     lensModel = SinglePlane(lens_model_list=['SIS'])
     density = lensModel.density(r=r, kwargs=[{'theta_E': theta_E}])
     sis = SIS()
     density_model = sis.density_lens(r=r, theta_E=theta_E)
     npt.assert_almost_equal(density, density_model, decimal=8)
예제 #5
0
class TestLensModel(object):
    """
    tests the source model routines
    """
    def setup(self):
        self.lensModel = SinglePlane(['GAUSSIAN'])
        self.kwargs = [{'amp': 1., 'sigma_x': 2., 'sigma_y': 2., 'center_x': 0., 'center_y': 0.}]

    def test_potential(self):
        output = self.lensModel.potential(x=1., y=1., kwargs=self.kwargs)
        assert output == 0.77880078307140488/(8*np.pi)

    def test_alpha(self):
        output1, output2 = self.lensModel.alpha(x=1., y=1., kwargs=self.kwargs)
        assert output1 == -0.19470019576785122/(8*np.pi)
        assert output2 == -0.19470019576785122/(8*np.pi)

    def test_ray_shooting(self):
        delta_x, delta_y = self.lensModel.ray_shooting(x=1., y=1., kwargs=self.kwargs)
        assert delta_x == 1 + 0.19470019576785122/(8*np.pi)
        assert delta_y == 1 + 0.19470019576785122/(8*np.pi)

    def test_mass_2d(self):
        lensModel = SinglePlane(['GAUSSIAN_KAPPA'])
        kwargs = [{'amp': 1., 'sigma': 2., 'center_x': 0., 'center_y': 0.}]
        output = lensModel.mass_2d(r=1, kwargs=kwargs)
        assert output == 0.11750309741540453

    def test_density(self):
        theta_E = 1
        r = 1
        lensModel = SinglePlane(lens_model_list=['SIS'])
        density = lensModel.density(r=r, kwargs=[{'theta_E': theta_E}])
        sis = SIS()
        density_model = sis.density_lens(r=r, theta_E=theta_E)
        npt.assert_almost_equal(density, density_model, decimal=8)

    def test_bool_list(self):
        lensModel = SinglePlane(['SPEP', 'SHEAR'])
        kwargs = [{'theta_E': 1, 'gamma': 2, 'e1': 0.1, 'e2': -0.1, 'center_x': 0, 'center_y': 0},
                           {'gamma1': 0.01, 'gamma2': -0.02}]
        alphax_1, alphay_1 = lensModel.alpha(1, 1, kwargs, k=0)
        alphax_1_list, alphay_1_list = lensModel.alpha(1, 1, kwargs, k=[0])
        npt.assert_almost_equal(alphax_1, alphax_1_list, decimal=5)
        npt.assert_almost_equal(alphay_1, alphay_1_list, decimal=5)

        alphax_1_1, alphay_1_1 = lensModel.alpha(1, 1, kwargs, k=0)
        alphax_1_2, alphay_1_2 = lensModel.alpha(1, 1, kwargs, k=1)
        alphax_full, alphay_full = lensModel.alpha(1, 1, kwargs, k=None)
        npt.assert_almost_equal(alphax_1_1 + alphax_1_2, alphax_full, decimal=5)
        npt.assert_almost_equal(alphay_1_1 + alphay_1_2, alphay_full, decimal=5)

    def test_init(self):
        lens_model_list = ['TNFW', 'TRIPLE_CHAMELEON', 'SHEAR_GAMMA_PSI', 'CURVED_ARC', 'NFW_MC',
                           'ARC_PERT','MULTIPOLE']
        lensModel = SinglePlane(lens_model_list=lens_model_list)
        assert lensModel.func_list[0].param_names[0] == 'Rs'
예제 #6
0
class MassProfile(object):
    """
    mass profile class, only works if all the profiles are at one single lens plane
    """
    def __init__(self,
                 profile_list,
                 kwargs_cosmo={
                     'D_d': 1000,
                     'D_s': 2000,
                     'D_ds': 500
                 },
                 interpol_grid_num=1000,
                 max_interpolate=100,
                 min_interpolate=0.001):
        """

        :param profile_list:
        """
        self.model = SinglePlane(profile_list)
        self.cosmo = Cosmo(**kwargs_cosmo)
        self._interp_grid_num = interpol_grid_num
        self._max_interpolate = max_interpolate
        self._min_interpolate = min_interpolate

    def mass_3d_interp(self, r, kwargs, new_compute=False):
        """

        :param r: in arc seconds
        :param kwargs: lens model parameters in arc seconds
        :return: mass enclosed physical radius in kg
        """
        if not hasattr(self, '_log_mass_3d') or new_compute is True:
            r_array = np.logspace(np.log10(self._min_interpolate),
                                  np.log10(self._max_interpolate),
                                  self._interp_grid_num)
            mass_3d_array = self.model.mass_3d(r_array, kwargs)
            mass_3d_array[mass_3d_array < 10.**(-10)] = 10.**(-10)
            mass_dim_array = mass_3d_array * const.arcsec ** 2 * self.cosmo.D_d * self.cosmo.D_s \
                       / self.cosmo.D_ds * const.Mpc * const.c ** 2 / (4 * np.pi * const.G)
            f = interp1d(np.log(r_array),
                         np.log(mass_dim_array / r_array),
                         fill_value="extrapolate")
            self._log_mass_3d = f
        return np.exp(self._log_mass_3d(np.log(r))) * r

    def mass_3d(self, r, kwargs):
        """
        mass enclosed a 3d radius

        :param r: in arc seconds
        :param kwargs: lens model parameters in arc seconds
        :return: mass enclosed physical radius in kg
        """
        mass_dimless = self.model.mass_3d(r, kwargs)
        mass_dim = mass_dimless * const.arcsec ** 2 * self.cosmo.D_d * self.cosmo.D_s \
                       / self.cosmo.D_ds * const.Mpc * const.c ** 2 / (4 * np.pi * const.G)
        return mass_dim
예제 #7
0
 def setup(self):
     self.lensModel = SinglePlane(['GAUSSIAN'])
     self.kwargs = [{
         'amp': 1.,
         'sigma_x': 2.,
         'sigma_y': 2.,
         'center_x': 0.,
         'center_y': 0.
     }]
예제 #8
0
class MassProfile(object):
    """
    mass profile class
    """
    def __init__(self,
                 profile_list,
                 kwargs_cosmo={
                     'D_d': 1000,
                     'D_s': 2000,
                     'D_ds': 500
                 },
                 kwargs_numerics={}):
        """

        :param profile_list:
        """
        kwargs_options = {'lens_model_list': profile_list}
        self.model = SinglePlane(profile_list)
        self.cosmo = Cosmo(kwargs_cosmo)
        self._interp_grid_num = kwargs_numerics.get('interpol_grid_num', 1000)
        self._max_interpolate = kwargs_numerics.get('max_integrate', 100)
        self._min_interpolate = kwargs_numerics.get('min_integrate', 0.0001)

    def mass_3d_interp(self, r, kwargs, new_compute=False):
        """

        :param r: in arc seconds
        :param kwargs: lens model parameters in arc seconds
        :return: mass enclosed physical radius in kg
        """
        if not hasattr(self, '_log_mass_3d') or new_compute is True:
            r_array = np.logspace(np.log10(self._min_interpolate),
                                  np.log10(self._max_interpolate),
                                  self._interp_grid_num)
            mass_3d_array = self.model.mass_3d(r_array, kwargs)
            mass_3d_array[mass_3d_array < 10.**(-10)] = 10.**(-10)
            mass_dim_array = mass_3d_array * const.arcsec ** 3 * self.cosmo.D_d ** 2 * self.cosmo.D_s \
                       / self.cosmo.D_ds * const.Mpc * const.c ** 2 / (4 * np.pi * const.G)
            f = interp1d(np.log(r_array),
                         np.log(mass_dim_array / r_array),
                         fill_value="extrapolate")
            self._log_mass_3d = f
        return np.exp(self._log_mass_3d(np.log(r))) * r

    def mass_3d(self, r, kwargs):
        """

        :param r: in arc seconds
        :param kwargs: lens model parameters in arc seconds
        :return: mass enclosed physical radius in kg
        """
        mass_dimless = self.model.mass_3d(r, kwargs)
        mass_dim = mass_dimless * const.arcsec ** 3 * self.cosmo.D_d ** 2 * self.cosmo.D_s \
                       / self.cosmo.D_ds * const.Mpc * const.c ** 2 / (4 * np.pi * const.G)
        return mass_dim
예제 #9
0
    def __init__(self,
                 z_source,
                 lens_model_list,
                 redshift_list,
                 cosmo=None,
                 **lensmodel_kwargs):
        """

        :param cosmo: instance of astropy.cosmology
        :return: Background class with instance of astropy.cosmology
        """
        if cosmo is None:
            from astropy.cosmology import default_cosmology
            cosmo = default_cosmology.get()
        self._cosmo_bkg = Background(cosmo)
        self._z_source = z_source
        if not len(lens_model_list) == len(redshift_list):
            raise ValueError(
                "The length of lens_model_list does not correspond to redshift_list"
            )
        self._lens_model_list = lens_model_list
        self._redshift_list = redshift_list
        if len(lens_model_list) < 1:
            self._sorted_redshift_index = []
        else:
            self._sorted_redshift_index = self._index_ordering(redshift_list)
        self._lens_model = SinglePlane(lens_model_list, **lensmodel_kwargs)
        z_before = 0
        self._T_ij_list = []
        self._T_z_list = []
        self._reduced2physical_factor = []
        for idex in self._sorted_redshift_index:
            z_lens = self._redshift_list[idex]
            if z_before == z_lens:
                delta_T = 0
            else:
                delta_T = self._cosmo_bkg.T_xy(z_before, z_lens)
            self._T_ij_list.append(delta_T)
            T_z = self._cosmo_bkg.T_xy(0, z_lens)
            self._T_z_list.append(T_z)
            factor = self._cosmo_bkg.D_xy(0, z_source) / self._cosmo_bkg.D_xy(
                z_lens, z_source)
            self._reduced2physical_factor.append(factor)
            z_before = z_lens
        delta_T = self._cosmo_bkg.T_xy(z_before, z_source)
        self._T_ij_list.append(delta_T)
        self._T_z_source = self._cosmo_bkg.T_xy(0, z_source)
        sum_partial = np.sum(self._T_ij_list)
        if np.abs(sum_partial - self._T_z_source) > 0.1:
            print(
                "Numerics in multi-plane compromised by too narrow spacing of too many redshift bins"
            )
예제 #10
0
    def test_raise(self):
        """
        check whether raises occurs if fastell4py is not installed

        :return:
        """
        if bool_test is False:
            with self.assertRaises(ImportError):
                SinglePlane(lens_model_list=['PEMD'])
            with self.assertRaises(ImportError):
                SinglePlane(lens_model_list=['SPEMD'])
        else:
            SinglePlane(lens_model_list=['PEMD', 'SPEMD'])
예제 #11
0
    def __init__(self, lens_model_list, z_source=None, redshift_list=None, cosmo=None, multi_plane=False):
        """

        :param lens_model_list: list of strings with lens model names
        :param foreground_shear: bool, when True, models a foreground non-linear shear distortion
        """
        self.lens_model_list = lens_model_list
        self.z_source = z_source
        self.redshift_list = redshift_list
        self.cosmo = cosmo
        self.multi_plane = multi_plane
        if multi_plane is True:
            self.lens_model = MultiLens(z_source, lens_model_list, redshift_list, cosmo=cosmo)
        else:
            self.lens_model = SinglePlane(lens_model_list)
예제 #12
0
 def test_init(self):
     lens_model_list = [
         'TNFW', 'TRIPLE_CHAMELEON', 'SHEAR_GAMMA_PSI', 'CURVED_ARC',
         'NFW_MC', 'ARC_PERT'
     ]
     lensModel = SinglePlane(lens_model_list=lens_model_list)
     assert lensModel.func_list[0].param_names[0] == 'Rs'
예제 #13
0
    def __init__(self,
                 lens_model_list,
                 kwargs_fixed,
                 kwargs_lower=None,
                 kwargs_upper=None,
                 num_images=0,
                 solver_type='NONE',
                 num_shapelet_lens=0):
        """

        :param kwargs_options:
        :param kwargs_fixed:
        """
        self.model_list = lens_model_list
        self.kwargs_fixed = kwargs_fixed
        self._num_images = num_images
        self._solver_type = solver_type
        self._num_shapelet_lens = num_shapelet_lens
        lens_model = SinglePlane(lens_model_list=lens_model_list)
        name_list = []
        for func in lens_model.func_list:
            name_list.append(func.param_names)
        self._param_name_list = name_list
        if kwargs_lower is None:
            kwargs_lower = []
            for func in lens_model.func_list:
                kwargs_lower.append(func.lower_limit_default)
        if kwargs_upper is None:
            kwargs_upper = []
            for func in lens_model.func_list:
                kwargs_upper.append(func.upper_limit_default)

        self.lower_limit = kwargs_lower
        self.upper_limit = kwargs_upper
예제 #14
0
    def __init__(self,
                 profile_list,
                 kwargs_cosmo={
                     'D_d': 1000,
                     'D_s': 2000,
                     'D_ds': 500
                 },
                 kwargs_numerics={}):
        """

        :param profile_list:
        """
        kwargs_options = {'lens_model_list': profile_list}
        self.model = SinglePlane(profile_list)
        self.cosmo = Cosmo(kwargs_cosmo)
        self._interp_grid_num = kwargs_numerics.get('interpol_grid_num', 1000)
        self._max_interpolate = kwargs_numerics.get('max_integrate', 100)
        self._min_interpolate = kwargs_numerics.get('min_integrate', 0.0001)
예제 #15
0
    def __init__(self, z_source, lens_model_list, redshift_list, cosmo=None):
        """

        :param cosmo: instance of astropy.cosmology
        :return: Background class with instance of astropy.cosmology
        """
        from astropy.cosmology import default_cosmology

        if cosmo is None:
            cosmo = default_cosmology.get()
        self._cosmo_bkg = Background(cosmo)
        self._z_source = z_source
        if not len(lens_model_list) == len(redshift_list):
            raise ValueError("The length of lens_model_list does not correspond to redshift_list")
        self._lens_model_list = lens_model_list
        self._redshift_list = redshift_list
        self._sorted_redshift_index = self._index_ordering(redshift_list)
        self._lens_model = SinglePlane(lens_model_list)
예제 #16
0
    def __init__(self,
                 kwargs_model,
                 kwargs_cosmo,
                 interpol_grid_num=1000,
                 log_integration=True,
                 max_integrate=1000,
                 min_integrate=0.0001,
                 max_light_draw=None,
                 lum_weight_int_method=True):
        """
        What we need:
        - max projected R to have ACCURATE I_R_sigma values
        - make sure everything outside cancels out (or is not rendered)

        :param interpol_grid_num: number of interpolation bins for integrand and interpolated functions
        :param log_integration: bool, if True, performs the numerical integral in log space distance (adviced)
         (only applies for lum_weight_int_method=True)
        :param max_integrate: maximum radius (in arc seconds) of the Jeans equation integral
         (assumes zero tracer particles outside this radius)
        :param max_light_draw: float; (optional) if set, draws up to this radius, else uses max_interpolate value
        :param lum_weight_int_method: bool, luminosity weighted dispersion integral to calculate LOS projected Jean's
         solution. ATTENTION: currently less accurate than 3d solution
        :param min_integrate:
        """
        mass_profile_list = kwargs_model.get('mass_profile_list')
        light_profile_list = kwargs_model.get('light_profile_list')
        anisotropy_model = kwargs_model.get('anisotropy_model')
        self._interp_grid_num = interpol_grid_num
        self._log_int = log_integration
        self._max_integrate = max_integrate  # maximal integration (and interpolation) in units of arcsecs
        self._min_integrate = min_integrate  # min integration (and interpolation) in units of arcsecs
        self._max_interpolate = max_integrate  # we chose to set the interpolation range to the integration range
        self._min_interpolate = min_integrate  # we chose to set the interpolation range to the integration range
        if max_light_draw is None:
            max_light_draw = max_integrate  # make sure the actual solution for the kinematics is only computed way inside the integral
        self.lightProfile = LightProfile(light_profile_list,
                                         interpol_grid_num=interpol_grid_num,
                                         max_interpolate=max_integrate,
                                         min_interpolate=min_integrate,
                                         max_draw=max_light_draw)
        Anisotropy.__init__(self, anisotropy_type=anisotropy_model)
        self.cosmo = Cosmo(**kwargs_cosmo)
        self._mass_profile = SinglePlane(mass_profile_list)
        self._lum_weight_int_method = lum_weight_int_method
예제 #17
0
    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):
        """

        :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 lens indexes that correspond to observed positions on the sky, not
        physical positions
        :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.
        """
        self.lens_model_list = lens_model_list
        self.z_lens = z_lens
        if z_source_convention is None:
            z_source_convention = z_source
        self.z_source = z_source
        self._z_source_convention = z_source_convention
        self.redshift_list = lens_redshift_list

        if cosmo is None:
            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)
        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=cosmo)
예제 #18
0
    def __init__(self,
                 profile_list,
                 kwargs_cosmo={
                     'D_d': 1000,
                     'D_s': 2000,
                     'D_ds': 500
                 },
                 interpol_grid_num=1000,
                 max_interpolate=100,
                 min_interpolate=0.001):
        """

        :param profile_list:
        """
        self.model = SinglePlane(profile_list)
        self.cosmo = Cosmo(**kwargs_cosmo)
        self._interp_grid_num = interpol_grid_num
        self._max_interpolate = max_interpolate
        self._min_interpolate = min_interpolate
예제 #19
0
    def test_bool_list(self):
        lensModel = SinglePlane(['SPEMD', 'SHEAR'])
        kwargs = [{
            'theta_E': 1,
            'gamma': 1,
            'e1': 0.1,
            'e2': -0.1,
            'center_x': 0,
            'center_y': 0
        }, {
            'e1': 0.01,
            'e2': -0.02
        }]
        alphax_1, alphay_1 = lensModel.alpha(1, 1, kwargs, k=0)
        alphax_1_list, alphay_1_list = lensModel.alpha(1, 1, kwargs, k=[0])
        npt.assert_almost_equal(alphax_1, alphax_1_list, decimal=5)
        npt.assert_almost_equal(alphay_1, alphay_1_list, decimal=5)

        alphax_1_1, alphay_1_1 = lensModel.alpha(1, 1, kwargs, k=0)
        alphax_1_2, alphay_1_2 = lensModel.alpha(1, 1, kwargs, k=1)
        alphax_full, alphay_full = lensModel.alpha(1, 1, kwargs, k=None)
        npt.assert_almost_equal(alphax_1_1 + alphax_1_2,
                                alphax_full,
                                decimal=5)
        npt.assert_almost_equal(alphay_1_1 + alphay_1_2,
                                alphay_full,
                                decimal=5)
예제 #20
0
    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)
예제 #21
0
    def __init__(self,
                 lens_model_list,
                 kwargs_fixed,
                 kwargs_lower=None,
                 kwargs_upper=None,
                 kwargs_logsampling=None,
                 num_images=0,
                 solver_type='NONE',
                 num_shapelet_lens=0):
        """

        :param lens_model_list: list of strings of lens model names
        :param kwargs_fixed: list of keyword arguments for model parameters to be held fixed
        :param kwargs_lower: list of keyword arguments of the lower bounds of the model parameters
        :param kwargs_upper: list of keyword arguments of the upper bounds of the model parameters
        :param kwargs_logsampling: list of keyword arguments of parameters to be sampled in log10 space
        :param num_images: number of images to be constrained by a non-linear solver
         (only relevant when shapelet potential functions are used)
        :param solver_type: string, type of non-linear solver
         (only relevant in this class when 'SHAPELETS' is the solver type)
        :param num_shapelet_lens: integer, number of shapelets in the lensing potential
         (only relevant when 'SHAPELET' lens model is used)
        """
        self.model_list = lens_model_list
        self.kwargs_fixed = kwargs_fixed
        self._num_images = num_images
        self._solver_type = solver_type
        self._num_shapelet_lens = num_shapelet_lens
        lens_model = SinglePlane(lens_model_list=lens_model_list)
        name_list = []
        for func in lens_model.func_list:
            name_list.append(func.param_names)
        self._param_name_list = name_list
        if kwargs_lower is None:
            kwargs_lower = []
            for func in lens_model.func_list:
                kwargs_lower.append(func.lower_limit_default)
        if kwargs_upper is None:
            kwargs_upper = []
            for func in lens_model.func_list:
                kwargs_upper.append(func.upper_limit_default)

        self.lower_limit = kwargs_lower
        self.upper_limit = kwargs_upper
        if kwargs_logsampling is None:
            kwargs_logsampling = [[] for i in range(len(self.model_list))]
        self.kwargs_logsampling = kwargs_logsampling
예제 #22
0
class NumericKinematics(Anisotropy):
    def __init__(self,
                 kwargs_model,
                 kwargs_cosmo,
                 interpol_grid_num=1000,
                 log_integration=True,
                 max_integrate=1000,
                 min_integrate=0.0001,
                 lum_weight_int_method=False):
        """
        What we need:
        - max projected R to have ACCURATE I_R_sigma values
        - make sure everything outside cancels out (or is not rendered)

        :param interpol_grid_num: number of interpolation bins for integrand and interpolated functions
        :param log_integration: bool, if True, performs the numerical integral in log space distance (adviced)
        :param max_integrate: maximum radius (in arc seconds) of the Jeans equation integral
         (assumes zero tracer particles outside this radius)
        :param lum_weight_int_method: bool, luminosity weighted dispersion integral to calculate LOS projected Jean's
         solution. ATTENTION: currently less accurate than 3d solution
        :param min_integrate:
        """
        mass_profile_list = kwargs_model.get('mass_profile_list')
        light_profile_list = kwargs_model.get('light_profile_list')
        anisotropy_model = kwargs_model.get('anisotropy_model')
        self._interp_grid_num = interpol_grid_num
        self._log_int = log_integration
        self._max_integrate = max_integrate  # maximal integration (and interpolation) in units of arcsecs
        self._min_integrate = min_integrate  # min integration (and interpolation) in units of arcsecs
        self._max_interpolate = max_integrate  # we chose to set the interpolation range to the integration range
        self._min_interpolate = min_integrate  # we chose to set the interpolation range to the integration range
        self.lightProfile = LightProfile(light_profile_list,
                                         interpol_grid_num=interpol_grid_num,
                                         max_interpolate=max_integrate,
                                         min_interpolate=min_integrate)
        Anisotropy.__init__(self, anisotropy_type=anisotropy_model)
        self.cosmo = Cosmo(**kwargs_cosmo)
        self._mass_profile = SinglePlane(mass_profile_list)
        self._lum_weight_int_method = lum_weight_int_method

    def sigma_s2(self, r, R, kwargs_mass, kwargs_light, kwargs_anisotropy):
        """
        returns unweighted los velocity dispersion for a specified projected radius

        :param r: 3d radius (not needed for this calculation)
        :param R: 2d projected radius (in angular units of arcsec)
        :param kwargs_mass: mass model parameters (following lenstronomy lens model conventions)
        :param kwargs_light: deflector light parameters (following lenstronomy light model conventions)
        :param kwargs_anisotropy: anisotropy parameters, may vary according to anisotropy type chosen.
            We refer to the Anisotropy() class for details on the parameters.
        :return: line-of-sight projected velocity dispersion at projected radius R
        """
        if self._lum_weight_int_method is True:
            return self.sigma_s2_project_int(r, R, kwargs_mass, kwargs_light,
                                             kwargs_anisotropy)
        else:
            return self.sigma_s2_full(r, R, kwargs_mass, kwargs_light,
                                      kwargs_anisotropy)

    def sigma_s2_project_int(self, r, R, kwargs_mass, kwargs_light,
                             kwargs_anisotropy):
        """
        returns unweighted los velocity dispersion for a specified projected radius

        :param r: 3d radius (not needed for this calculation)
        :param R: 2d projected radius (in angular units of arcsec)
        :param kwargs_mass: mass model parameters (following lenstronomy lens model conventions)
        :param kwargs_light: deflector light parameters (following lenstronomy light model conventions)
        :param kwargs_anisotropy: anisotropy parameters, may vary according to anisotropy type chosen.
            We refer to the Anisotropy() class for details on the parameters.
        :return: line-of-sight projected velocity dispersion at projected radius R
        """
        # TODO: this is potentially inaccurate as the light-only integral is analytically to infinity while the
        # nominator is numerically to a finite distance, so luminosity weighting might be off
        # this could lead to an under-prediction of the velocity dispersion
        I_R_sigma2 = self._I_R_sigma2_interp(R, kwargs_mass, kwargs_light,
                                             kwargs_anisotropy)
        I_R = self.lightProfile.light_2d(R, kwargs_light)
        return np.nan_to_num(I_R_sigma2 / I_R)

    def sigma_s2_full(self, r, R, kwargs_mass, kwargs_light,
                      kwargs_anisotropy):
        """
        returns unweighted los velocity dispersion for a specified projected radius

        :param r: 3d radius (not needed for this calculation)
        :param R: 2d projected radius (in angular units of arcsec)
        :param kwargs_mass: mass model parameters (following lenstronomy lens model conventions)
        :param kwargs_light: deflector light parameters (following lenstronomy light model conventions)
        :param kwargs_anisotropy: anisotropy parameters, may vary according to anisotropy type chosen.
            We refer to the Anisotropy() class for details on the parameters.
        :return: line-of-sight projected velocity dispersion at projected radius R from 3d radius r
        """
        beta = self.beta_r(r, **kwargs_anisotropy)
        return (1 - beta * R**2 / r**2) * self.sigma_r2(
            r, kwargs_mass, kwargs_light, kwargs_anisotropy)

    def sigma_r2(self, r, kwargs_mass, kwargs_light, kwargs_anisotropy):
        """
        computes numerically the solution of the Jeans equation for a specific 3d radius
        E.g. Equation (A1) of Mamon & Lokas https://arxiv.org/pdf/astro-ph/0405491.pdf
        l(r) \sigma_r(r) ^ 2 =  1/f(r) \int_r^{\infty} f(s) l(s) G M(s) / s^2 ds
        where l(r) is the 3d light profile
        M(s) is the enclosed 3d mass
        f is the solution to
        d ln(f)/ d ln(r) = 2 beta(r)

        :param r: 3d radius
        :param kwargs_mass: mass model parameters (following lenstronomy lens model conventions)
        :param kwargs_light: deflector light parameters (following lenstronomy light model conventions)
        :param kwargs_anisotropy: anisotropy parameters, may vary according to anisotropy type chosen.
            We refer to the Anisotropy() class for details on the parameters.
        :return: sigma_r**2
        """
        l_r = self.lightProfile.light_3d_interp(r, kwargs_light)
        f_r = self.anisotropy_solution(r, **kwargs_anisotropy)
        return 1 / f_r / l_r * self._jeans_solution_integral(
            r, kwargs_mass, kwargs_light, kwargs_anisotropy) * const.G / (
                const.arcsec * self.cosmo.dd * const.Mpc)

    def mass_3d(self, r, kwargs):
        """
        mass enclosed a 3d radius

        :param r: in arc seconds
        :param kwargs: lens model parameters in arc seconds
        :return: mass enclosed physical radius in kg
        """
        mass_dimless = self._mass_profile.mass_3d(r, kwargs)
        mass_dim = mass_dimless * const.arcsec ** 2 * self.cosmo.dd * self.cosmo.ds / self.cosmo.dds * const.Mpc * \
                   const.c ** 2 / (4 * np.pi * const.G)
        return mass_dim

    def grav_potential(self, r, kwargs_mass):
        """
        Gravitational potential in SI units

        :param r: radius (arc seconds)
        :param kwargs_mass:
        :return: gravitational potential
        """
        mass_dim = self.mass_3d(r, kwargs_mass)
        grav_pot = -const.G * mass_dim / (r * const.arcsec * self.cosmo.dd *
                                          const.Mpc)
        return grav_pot

    def draw_light(self, kwargs_light):
        """

        :param kwargs_light: keyword argument (list) of the light model
        :return: 3d radius (if possible), 2d projected radius, x-projected coordinate, y-projected coordinate
        """
        r = self.lightProfile.draw_light_3d(kwargs_light, n=1)[0]
        R, x, y = util.project2d_random(r)

        # this code is a remnant of the 2d-only rendering
        # (can be used when accurate luminosity-weighted integrated velocity dispersion predictions are made)
        # R = self.lightProfile.draw_light_2d(kwargs_light, n=1)[0]
        # x, y = util.draw_xy(R)
        # r = None
        return r, R, x, y

    def delete_cache(self):
        """
        delete interpolation function for a specific mass and light profile as well as for a specific anisotropy model

        :return:
        """
        if hasattr(self, '_log_mass_3d'):
            del self._log_mass_3d
        if hasattr(self, '_interp_jeans_integral'):
            del self._interp_jeans_integral
        if hasattr(self, '_interp_I_R_sigma2'):
            del self._interp_I_R_sigma2
        self.lightProfile.delete_cache()
        self.delete_anisotropy_cache()

    def _I_R_sigma2(self, R, kwargs_mass, kwargs_light, kwargs_anisotropy):
        """
        equation A15 in Mamon&Lokas 2005 as a logarithmic numerical integral (if option is chosen)

        :param R: 2d projected radius (in angular units)
        :param kwargs_mass: mass model parameters (following lenstronomy lens model conventions)
        :param kwargs_light: deflector light parameters (following lenstronomy light model conventions)
        :param kwargs_anisotropy: anisotropy parameters, may vary according to anisotropy type chosen.
            We refer to the Anisotropy() class for details on the parameters.
        :return: integral of A15 in Mamon&Lokas 2005
        """
        R = max(R, self._min_integrate)
        max_integrate = self._max_integrate  # make sure the integration of the Jeans equation is performed further out than the interpolation
        if self._log_int is True:
            min_log = np.log10(R + 0.001)
            max_log = np.log10(max_integrate)
            r_array = np.logspace(min_log, max_log, self._interp_grid_num)
            dlog_r = (np.log10(r_array[2]) - np.log10(r_array[1])) * np.log(10)
            IR_sigma2_dr = self._integrand_A15(
                r_array, R, kwargs_mass, kwargs_light,
                kwargs_anisotropy) * dlog_r * r_array
        else:
            r_array = np.linspace(R + 0.001, max_integrate,
                                  self._interp_grid_num)
            dr = r_array[2] - r_array[1]
            IR_sigma2_dr = self._integrand_A15(
                r_array, R, kwargs_mass, kwargs_light, kwargs_anisotropy) * dr
        IR_sigma2 = np.sum(
            IR_sigma2_dr)  # integral from angle to physical scales
        return IR_sigma2 * 2 * const.G / (const.arcsec * self.cosmo.dd *
                                          const.Mpc)

    def _I_R_sigma2_interp(self, R, kwargs_mass, kwargs_light,
                           kwargs_anisotropy):
        """
        equation A15 in Mamon&Lokas 2005 as interpolation in log space

        :param R: projected radius
        :param kwargs_mass: mass profile keyword arguments
        :param kwargs_light: light model keyword arguments
        :param kwargs_anisotropy: stellar anisotropy keyword arguments
        :return:
        """
        if not hasattr(self, '_interp_I_R_sigma2'):
            min_log = np.log10(self._min_integrate)
            max_log = np.log10(self._max_integrate)
            R_array = np.logspace(min_log, max_log, self._interp_grid_num)
            I_R_sigma2_array = []
            for R_i in R_array:
                I_R_sigma2_array.append(
                    self._I_R_sigma2(R_i, kwargs_mass, kwargs_light,
                                     kwargs_anisotropy))
            self._interp_I_R_sigma2 = interp1d(np.log(R_array),
                                               np.array(I_R_sigma2_array),
                                               fill_value="extrapolate")
        return self._interp_I_R_sigma2(np.log(R))

    def _integrand_A15(self, r, R, kwargs_mass, kwargs_light,
                       kwargs_anisotropy):
        """
        integrand of A15 (in log space) in Mamon&Lokas 2005

        :param r: 3d radius in arc seconds
        :param R: 2d projected radius
        :param kwargs_mass: mass model parameters (following lenstronomy lens model conventions)
        :param kwargs_light: deflector light parameters (following lenstronomy light model conventions)
        :param kwargs_anisotropy: anisotropy parameters, may vary according to anisotropy type chosen.
            We refer to the Anisotropy() class for details on the parameters.
        :return:
        """
        k_r = self.K(r, R, **kwargs_anisotropy)
        l_r = self.lightProfile.light_3d_interp(r, kwargs_light)
        m_r = self._mass_3d_interp(r, kwargs_mass)
        out = k_r * l_r * m_r / r
        return out

    def _jeans_solution_integral(self, r, kwargs_mass, kwargs_light,
                                 kwargs_anisotropy):
        """
        interpolated solution of the integral \int_r^{\infty} f(s) l(s) G M(s) / s^2 ds

        :param r: 3d radius
        :param kwargs_mass: mass profile keyword arguments
        :param kwargs_light: light profile keyword arguments
        :param kwargs_anisotropy: anisotropy keyword arguments
        :return: interpolated solution of the Jeans integral
         (copped values at large radius as they become numerically inaccurate)
        """
        if not hasattr(self, '_interp_jeans_integral'):
            min_log = np.log10(self._min_integrate)
            max_log = np.log10(
                self._max_integrate
            )  # we extend the integral but ignore these outer solutions in the interpolation
            r_array = np.logspace(min_log, max_log, self._interp_grid_num)
            dlog_r = (np.log10(r_array[2]) - np.log10(r_array[1])) * np.log(10)
            integrand_jeans = self._integrand_jeans_solution(
                r_array, kwargs_mass, kwargs_light,
                kwargs_anisotropy) * dlog_r * r_array
            #flip array from inf to finite
            integral_jeans_r = np.cumsum(np.flip(integrand_jeans))
            #flip array back
            integral_jeans_r = np.flip(integral_jeans_r)
            #call 1d interpolation function
            self._interp_jeans_integral = interp1d(
                np.log(r_array[r_array <= self._max_integrate]),
                integral_jeans_r[r_array <= self._max_integrate],
                fill_value="extrapolate")
        return self._interp_jeans_integral(np.log(r))

    def _integrand_jeans_solution(self, r, kwargs_mass, kwargs_light,
                                  kwargs_anisotropy):
        """
        integrand of A1 (in log space) in Mamon&Lokas 2005 to calculate the Jeans equation numerically
        f(s) l(s) M(s) / s^2

        :param r:
        :param kwargs_mass:
        :param kwargs_light:
        :param kwargs_anisotropy:
        :return:
        """
        f_r = self.anisotropy_solution(r, **kwargs_anisotropy)
        l_r = self.lightProfile.light_3d_interp(r, kwargs_light)
        m_r = self._mass_3d_interp(r, kwargs_mass)
        out = f_r * l_r * m_r / r**2
        return out

    def _mass_3d_interp(self, r, kwargs, new_compute=False):
        """

        :param r: in arc seconds
        :param kwargs: lens model parameters in arc seconds
        :param new_compute: bool, if True, recomputes the interpolation
        :return: mass enclosed physical radius in kg
        """
        if not hasattr(self, '_log_mass_3d') or new_compute is True:
            r_array = np.logspace(np.log10(self._min_interpolate),
                                  np.log10(self._max_interpolate),
                                  self._interp_grid_num)
            mass_3d_array = self.mass_3d(r_array, kwargs)
            mass_3d_array[mass_3d_array < 10.**(-10)] = 10.**(-10)
            #mass_dim_array = mass_3d_array * const.arcsec ** 2 * self.cosmo.dd * self.cosmo.ds \
            #                 / self.cosmo.dds * const.Mpc * const.c ** 2 / (4 * np.pi * const.G)
            self._log_mass_3d = interp1d(np.log(r_array),
                                         np.log(mass_3d_array / r_array),
                                         fill_value="extrapolate")
        return np.exp(self._log_mass_3d(np.log(r))) * r
예제 #23
0
class MultiPlane(object):
    """
    Multi-plane lensing class
    """
    def __init__(self,
                 z_source,
                 lens_model_list,
                 redshift_list,
                 cosmo=None,
                 **lensmodel_kwargs):
        """

        :param cosmo: instance of astropy.cosmology
        :return: Background class with instance of astropy.cosmology
        """
        if cosmo is None:
            from astropy.cosmology import default_cosmology
            cosmo = default_cosmology.get()
        self._cosmo_bkg = Background(cosmo)
        self._z_source = z_source
        if not len(lens_model_list) == len(redshift_list):
            raise ValueError(
                "The length of lens_model_list does not correspond to redshift_list"
            )
        self._lens_model_list = lens_model_list
        self._redshift_list = redshift_list
        if len(lens_model_list) < 1:
            self._sorted_redshift_index = []
        else:
            self._sorted_redshift_index = self._index_ordering(redshift_list)
        self._lens_model = SinglePlane(lens_model_list, **lensmodel_kwargs)
        z_before = 0
        self._T_ij_list = []
        self._T_z_list = []
        self._reduced2physical_factor = []
        for idex in self._sorted_redshift_index:
            z_lens = self._redshift_list[idex]
            if z_before == z_lens:
                delta_T = 0
            else:
                delta_T = self._cosmo_bkg.T_xy(z_before, z_lens)
            self._T_ij_list.append(delta_T)
            T_z = self._cosmo_bkg.T_xy(0, z_lens)
            self._T_z_list.append(T_z)
            factor = self._cosmo_bkg.D_xy(0, z_source) / self._cosmo_bkg.D_xy(
                z_lens, z_source)
            self._reduced2physical_factor.append(factor)
            z_before = z_lens
        delta_T = self._cosmo_bkg.T_xy(z_before, z_source)
        self._T_ij_list.append(delta_T)
        self._T_z_source = self._cosmo_bkg.T_xy(0, z_source)
        sum_partial = np.sum(self._T_ij_list)
        if np.abs(sum_partial - self._T_z_source) > 0.1:
            print(
                "Numerics in multi-plane compromised by too narrow spacing of too many redshift bins"
            )

    def ray_shooting(self, theta_x, theta_y, kwargs_lens, k=None):
        """
        ray-tracing (backwards light cone)

        :param theta_x: angle in x-direction on the image
        :param theta_y: angle in y-direction on the image
        :param kwargs_lens:
        :return: angles in the source plane
        """
        x = np.zeros_like(theta_x)
        y = np.zeros_like(theta_y)
        alpha_x = theta_x
        alpha_y = theta_y
        i = -1
        for i, idex in enumerate(self._sorted_redshift_index):
            delta_T = self._T_ij_list[i]
            x, y = self._ray_step(x, y, alpha_x, alpha_y, delta_T)
            alpha_x, alpha_y = self._add_deflection(x, y, alpha_x, alpha_y,
                                                    kwargs_lens, i)
        delta_T = self._T_ij_list[i + 1]
        x, y = self._ray_step(x, y, alpha_x, alpha_y, delta_T)
        beta_x, beta_y = self._co_moving2angle_source(x, y)
        return beta_x, beta_y

    def ray_shooting_partial(self,
                             x,
                             y,
                             alpha_x,
                             alpha_y,
                             z_start,
                             z_stop,
                             kwargs_lens,
                             keep_range=False,
                             include_z_start=False):
        """
        ray-tracing through parts of the coin, starting with (x,y) and angles (alpha_x, alpha_y) at redshift z_start
        and then backwards to redshfit z_stop

        :param x: co-moving position [Mpc]
        :param y: co-moving position [Mpc]
        :param alpha_x: ray angle at z_start [arcsec]
        :param alpha_y: ray angle at z_start [arcsec]
        :param z_start: redshift of start of computation
        :param z_stop: redshift where output is computed
        :param kwargs_lens: lens model keyword argument list
        :param keep_range: bool, if True, only computes the angular diameter ratio between the first and last step once
        :return: co-moving position and angles at redshift z_stop
        """
        z_lens_last = z_start
        first_deflector = True
        for i, idex in enumerate(self._sorted_redshift_index):
            z_lens = self._redshift_list[idex]
            if self._start_condition(include_z_start, z_lens,
                                     z_start) and z_lens <= z_stop:
                #if z_lens > z_start and z_lens <= z_stop:
                if first_deflector is True:
                    if keep_range is True:
                        if not hasattr(self, '_cosmo_bkg_T_start'):
                            self._cosmo_bkg_T_start = self._cosmo_bkg.T_xy(
                                z_start, z_lens)
                        delta_T = self._cosmo_bkg_T_start
                    else:
                        delta_T = self._cosmo_bkg.T_xy(z_start, z_lens)
                    first_deflector = False
                else:
                    delta_T = self._T_ij_list[i]
                x, y = self._ray_step(x, y, alpha_x, alpha_y, delta_T)
                alpha_x, alpha_y = self._add_deflection(
                    x, y, alpha_x, alpha_y, kwargs_lens, i)
                z_lens_last = z_lens
        if keep_range is True:
            if not hasattr(self, '_cosmo_bkg_T_stop'):
                self._cosmo_bkg_T_stop = self._cosmo_bkg.T_xy(
                    z_lens_last, z_stop)
            delta_T = self._cosmo_bkg_T_stop
        else:
            delta_T = self._cosmo_bkg.T_xy(z_lens_last, z_stop)

        x, y = self._ray_step(x, y, alpha_x, alpha_y, delta_T)
        return x, y, alpha_x, alpha_y

    def ray_shooting_partial_steps(self,
                                   x,
                                   y,
                                   alpha_x,
                                   alpha_y,
                                   z_start,
                                   z_stop,
                                   kwargs_lens,
                                   include_z_start=False):
        """
        ray-tracing through parts of the coin, starting with (x,y) and angles (alpha_x, alpha_y) at redshift z_start
        and then backwards to redshfit z_stop.

        This function differs from 'ray_shooting_partial' in that it returns the angular position of the ray
        at each lens plane.

        :param x: co-moving position [Mpc]
        :param y: co-moving position [Mpc]
        :param alpha_x: ray angle at z_start [arcsec]
        :param alpha_y: ray angle at z_start [arcsec]
        :param z_start: redshift of start of computation
        :param z_stop: redshift where output is computed
        :param kwargs_lens: lens model keyword argument list
        :param keep_range: bool, if True, only computes the angular diameter ratio between the first and last step once
        :return: co-moving position and angles at redshift z_stop
        """
        z_lens_last = z_start
        first_deflector = True

        pos_x, pos_y, redshifts, Tz_list = [], [], [], []
        pos_x.append(x)
        pos_y.append(y)
        redshifts.append(z_start)
        Tz_list.append(self._cosmo_bkg.T_xy(0, z_start))

        current_z = z_lens_last

        for i, idex in enumerate(self._sorted_redshift_index):

            z_lens = self._redshift_list[idex]

            if self._start_condition(include_z_start, z_lens,
                                     z_start) and z_lens <= z_stop:

                if z_lens != current_z:
                    new_plane = True
                    current_z = z_lens

                else:
                    new_plane = False

                if first_deflector is True:
                    delta_T = self._cosmo_bkg.T_xy(z_start, z_lens)

                    first_deflector = False
                else:
                    delta_T = self._T_ij_list[i]
                x, y = self._ray_step(x, y, alpha_x, alpha_y, delta_T)
                alpha_x, alpha_y = self._add_deflection(
                    x, y, alpha_x, alpha_y, kwargs_lens, i)
                z_lens_last = z_lens

                if new_plane:

                    pos_x.append(x)
                    pos_y.append(y)
                    redshifts.append(z_lens)
                    Tz_list.append(self._T_z_list[i])

        delta_T = self._cosmo_bkg.T_xy(z_lens_last, z_stop)

        x, y = self._ray_step(x, y, alpha_x, alpha_y, delta_T)

        pos_x.append(x)
        pos_y.append(y)
        redshifts.append(self._z_source)
        Tz_list.append(self._T_z_source)

        return pos_x, pos_y, redshifts, Tz_list

    def arrival_time(self, theta_x, theta_y, kwargs_lens, k=None):
        """
        light travel time relative to a straight path through the coordinate (0,0)
        Negative sign means earlier arrival time

        :param theta_x: angle in x-direction on the image
        :param theta_y: angle in y-direction on the image
        :param kwargs_lens:
        :return: travel time in unit of days
        """
        dt_grav = np.zeros_like(theta_x)
        dt_geo = np.zeros_like(theta_x)
        x = np.zeros_like(theta_x)
        y = np.zeros_like(theta_y)
        alpha_x = theta_x
        alpha_y = theta_y
        i = 0
        for i, idex in enumerate(self._sorted_redshift_index):
            z_lens = self._redshift_list[idex]
            delta_T = self._T_ij_list[i]
            dt_geo_new = self._geometrical_delay(alpha_x, alpha_y, delta_T)
            x, y = self._ray_step(x, y, alpha_x, alpha_y, delta_T)
            dt_grav_new = self._gravitational_delay(x, y, kwargs_lens, i,
                                                    z_lens)
            alpha_x, alpha_y = self._add_deflection(x, y, alpha_x, alpha_y,
                                                    kwargs_lens, i)
            dt_geo = dt_geo + dt_geo_new
            dt_grav = dt_grav + dt_grav_new
        delta_T = self._T_ij_list[i + 1]
        dt_geo += self._geometrical_delay(alpha_x, alpha_y, delta_T)
        x, y = self._ray_step(x, y, alpha_x, alpha_y, delta_T)
        beta_x, beta_y = self._co_moving2angle_source(x, y)
        dt_geo -= self._geometrical_delay(beta_x, beta_y, self._T_z_source)
        return dt_grav + dt_geo

    def alpha(self, theta_x, theta_y, kwargs_lens, k=None):
        """
        reduced deflection angle

        :param theta_x: angle in x-direction
        :param theta_y: angle in y-direction
        :param kwargs_lens: lens model kwargs
        :return:
        """
        beta_x, beta_y = self.ray_shooting(theta_x, theta_y, kwargs_lens)
        alpha_x = theta_x - beta_x
        alpha_y = theta_y - beta_y
        return alpha_x, alpha_y

    def hessian(self, theta_x, theta_y, kwargs_lens, k=None, diff=0.00000001):
        """
        computes the hessian components f_xx, f_yy, f_xy from f_x and f_y with numerical differentiation

        :param theta_x: x-position (preferentially arcsec)
        :type theta_x: numpy array
        :param theta_y: y-position (preferentially arcsec)
        :type theta_y: numpy array
        :param kwargs_lens: list of keyword arguments of lens model parameters matching the lens model classes
        :param diff: numerical differential step (float)
        :return: f_xx, f_xy, f_yx, f_yy
        """

        alpha_ra, alpha_dec = self.alpha(theta_x, theta_y, kwargs_lens)

        alpha_ra_dx, alpha_dec_dx = self.alpha(theta_x + diff, theta_y,
                                               kwargs_lens)
        alpha_ra_dy, alpha_dec_dy = self.alpha(theta_x, theta_y + diff,
                                               kwargs_lens)

        dalpha_rara = (alpha_ra_dx - alpha_ra) / diff
        dalpha_radec = (alpha_ra_dy - alpha_ra) / diff
        dalpha_decra = (alpha_dec_dx - alpha_dec) / diff
        dalpha_decdec = (alpha_dec_dy - alpha_dec) / 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 _index_ordering(self, redshift_list):
        """

        :param redshift_list: list of redshifts
        :return: indexes in acending order to be evaluated (from z=0 to z=z_source)
        """
        redshift_list = np.array(redshift_list)
        sort_index = np.argsort(redshift_list[redshift_list < self._z_source])
        if len(sort_index) < 1:
            raise ValueError(
                "There is no lens object between observer at z=0 and source at z=%s"
                % self._z_source)
        return sort_index

    def _reduced2physical_deflection(self, alpha_reduced, idex_lens):
        """
        alpha_reduced = D_ds/Ds alpha_physical

        :param alpha_reduced: reduced deflection angle
        :param z_lens: lens redshift
        :param z_source: source redshift
        :return: physical deflection angle
        """
        factor = self._reduced2physical_factor[idex_lens]
        #factor = self._cosmo_bkg.D_xy(0, z_source) / self._cosmo_bkg.D_xy(z_lens, z_source)
        return alpha_reduced * factor

    def _gravitational_delay(self, x, y, kwargs_lens, idex, z_lens):
        """

        :param x: co-moving coordinate at the lens plane
        :param y: co-moving coordinate at the lens plane
        :param kwargs_lens: lens model keyword arguments
        :param z_lens: redshift of the deflector
        :param idex: index of the lens model
        :return: gravitational delay in units of days as seen at z=0
        """
        theta_x, theta_y = self._co_moving2angle(x, y, idex)
        potential = self._lens_model.potential(
            theta_x, theta_y, kwargs_lens, k=self._sorted_redshift_index[idex])
        delay_days = self._lensing_potential2time_delay(
            potential, z_lens, z_source=self._z_source)
        return -delay_days

    def _geometrical_delay(self, alpha_x, alpha_y, delta_T):
        """
        geometrical delay (evaluated at z=0) of a light ray with an angle relative to the shortest path

        :param alpha_x: angle relative to a straight path
        :param alpha_y: angle relative to a straight path
        :param delta_T: transversal diameter distance between the start and end of the ray
        :return: geometrical delay in units of days
        """
        dt_days = (
            alpha_x**2 + alpha_y**2
        ) / 2. * delta_T * const.Mpc / const.c / const.day_s * const.arcsec**2
        return dt_days

    def _lensing_potential2time_delay(self, potential, z_lens, z_source):
        """
        transforms the lensing potential (in units arcsec^2) to a gravitational time-delay as measured at z=0

        :param potential: lensing potential
        :param z_lens: redshift of the deflector
        :param z_source: redshift of source for the definition of the lensing quantities
        :return: gravitational time-delay in units of days
        """
        D_dt = self._cosmo_bkg.D_dt(z_lens, z_source)
        delay_days = const.delay_arcsec2days(potential, D_dt)
        return delay_days

    def _co_moving2angle(self, x, y, idex):
        """
        transforms co-moving distances Mpc into angles on the sky (radian)

        :param x: co-moving distance
        :param y: co-moving distance
        :param z_lens: redshift of plane
        :return: angles on the sky
        """
        T_z = self._T_z_list[idex]
        #T_z = self._cosmo_bkg.T_xy(0, z_lens)
        theta_x = x / T_z
        theta_y = y / T_z
        return theta_x, theta_y

    def _co_moving2angle_source(self, x, y):
        """
        special case of the co_moving2angle definition at the source redshift

        :param x:
        :param y:
        :return:
        """
        T_z = self._T_z_source
        theta_x = x / T_z
        theta_y = y / T_z
        return theta_x, theta_y

    def _ray_step(self, x, y, alpha_x, alpha_y, delta_T):
        """
        ray propagation with small angle approximation

        :param x: co-moving x-position
        :param y: co-moving y-position
        :param alpha_x: deflection angle in x-direction at (x, y)
        :param alpha_y: deflection angle in y-direction at (x, y)
        :param delta_T: transversal angular diameter distance to the next step
        :return:
        """
        x_ = x + alpha_x * delta_T
        y_ = y + alpha_y * delta_T
        return x_, y_

    def _add_deflection(self, x, y, alpha_x, alpha_y, kwargs_lens, idex):
        """
        adds the pyhsical deflection angle of a single lens plane to the deflection field

        :param x: co-moving distance at the deflector plane
        :param y: co-moving distance at the deflector plane
        :param alpha_x: physical angle (radian) before the deflector plane
        :param alpha_y: physical angle (radian) before the deflector plane
        :param kwargs_lens: lens model parameter kwargs
        :param idex: index of the lens model to be added
        :param idex_lens: redshift of the deflector plane
        :return: updated physical deflection after deflector plane (in a backwards ray-tracing perspective)
        """
        theta_x, theta_y = self._co_moving2angle(x, y, idex)
        alpha_x_red, alpha_y_red = self._lens_model.alpha(
            theta_x, theta_y, kwargs_lens, k=self._sorted_redshift_index[idex])
        alpha_x_phys = self._reduced2physical_deflection(alpha_x_red, idex)
        alpha_y_phys = self._reduced2physical_deflection(alpha_y_red, idex)
        alpha_x_new = alpha_x - alpha_x_phys
        alpha_y_new = alpha_y - alpha_y_phys
        return alpha_x_new, alpha_y_new

    def _start_condition(self, inclusive, z_lens, z_start):

        if inclusive:
            return z_lens >= z_start
        else:
            return z_lens > z_start
예제 #24
0
    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)
예제 #25
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
예제 #26
0
class LensModel(object):
    """
    class to handle an arbitrary list of lens models
    """

    def __init__(self, lens_model_list, z_source=None, redshift_list=None, cosmo=None, multi_plane=False):
        """

        :param lens_model_list: list of strings with lens model names
        :param foreground_shear: bool, when True, models a foreground non-linear shear distortion
        """
        self.lens_model_list = lens_model_list
        self.z_source = z_source
        self.redshift_list = redshift_list
        self.cosmo = cosmo
        self.multi_plane = multi_plane
        if multi_plane is True:
            self.lens_model = MultiLens(z_source, lens_model_list, redshift_list, cosmo=cosmo)
        else:
            self.lens_model = SinglePlane(lens_model_list)


    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 self.multi_plane:
            raise ValueError("Fermat potential is not defined in multi-plane lensing. Please use single plane lens models.")
        else:
            return self.lens_model.fermat_potential(x_image, y_image, x_source, y_source, kwargs_lens)

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

        :param x_image:
        :param y_image:
        :param kwargs_lens:
        :return:
        """
        if self.multi_plane:
            return self.lens_model.arrival_time(x_image, y_image, kwargs_lens)
        else:
            raise ValueError(
                "arrival_time routine not defined for single plane lensing. Please use Fermat potential instead")

    def mass(self, x, y, epsilon_crit, kwargs):
        """

        :param x: position
        :param y: position
        :param epsilon_crit: critical mass density of a lens
        :param kwargs: list of keyword arguments of lens model parameters matching the lens model classes
        :return: projected mass density in units of input epsilon_crit
        """
        kappa = self.kappa(x, y, kwargs)
        mass = epsilon_crit * kappa
        return mass

    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 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_yy = self.hessian(x, y, kwargs, k=k)
        kappa = 1./2 * (f_xx + f_yy)  # attention on units
        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_yy = self.hessian(x, y, kwargs, k=k)
        gamma1 = 1./2 * (f_xx - f_yy)  # attention on units
        gamma2 = f_xy  # attention on units
        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_yy = self.hessian(x, y, kwargs, k=k)
        det_A = (1 - f_xx) * (1 - f_yy) - f_xy*f_xy
        return 1./det_A  # attention, if dividing by zero

    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 mass_3d(self, r, kwargs, bool_list=None):
        """
        computes the mass within a 3d sphere of radius r

        :param r: radius (in angular units)
        :param kwargs: list of keyword arguments of lens model parameters matching the lens model classes
        :param bool_list: list of bools that are part of the output
        :return: mass (in angular units, modulo epsilon_crit)
        """
        if self.multi_plane is True:
            raise ValueError("mass_3d is not supported for multi-lane lensing. Please use single plane instead.")
        else:
            return self.lens_model.mass_3d(r, kwargs, bool_list=bool_list)

    def mass_2d(self, r, kwargs, bool_list=None):
        """
        computes the mass enclosed a projected (2d) radius r

        :param r: radius (in angular units)
        :param kwargs: list of keyword arguments of lens model parameters matching the lens model classes
        :param bool_list: list of bools that are part of the output
        :return: projected mass (in angular units, modulo epsilon_crit)
        """
        if self.multi_plane is True:
            raise ValueError("mass_2d is not supported for multi-lane lensing. Please use single plane instead.")
        else:
            return self.lens_model.mass_2d(r, kwargs, bool_list=bool_list)
예제 #27
0
class MultiLens(object):
    """
    Multi-plane lensing class
    """

    def __init__(self, z_source, lens_model_list, redshift_list, cosmo=None):
        """

        :param cosmo: instance of astropy.cosmology
        :return: Background class with instance of astropy.cosmology
        """
        from astropy.cosmology import default_cosmology

        if cosmo is None:
            cosmo = default_cosmology.get()
        self._cosmo_bkg = Background(cosmo)
        self._z_source = z_source
        if not len(lens_model_list) == len(redshift_list):
            raise ValueError("The length of lens_model_list does not correspond to redshift_list")
        self._lens_model_list = lens_model_list
        self._redshift_list = redshift_list
        self._sorted_redshift_index = self._index_ordering(redshift_list)
        self._lens_model = SinglePlane(lens_model_list)

    def ray_shooting(self, theta_x, theta_y, kwargs_lens, k=None):
        """
        ray-tracing (backwards light cone)

        :param theta_x: angle in x-direction on the image
        :param theta_y: angle in y-direction on the image
        :param kwargs_lens:
        :return: angles in the source plane
        """
        x = np.zeros_like(theta_x)
        y = np.zeros_like(theta_y)
        z_before = 0
        alpha_x = theta_x
        alpha_y = theta_y
        for idex in self._sorted_redshift_index:
            z_lens = self._redshift_list[idex]
            delta_T = self._cosmo_bkg.T_xy(z_before, z_lens)
            x, y = self._ray_step(x, y, alpha_x, alpha_y, delta_T)
            alpha_x, alpha_y = self._add_deflection(x, y, alpha_x, alpha_y, kwargs_lens, idex, z_lens)
            z_before = z_lens
        delta_T = self._cosmo_bkg.T_xy(z_before, self._z_source)
        x, y = self._ray_step(x, y, alpha_x, alpha_y, delta_T)
        beta_x, beta_y = self._co_moving2angle(x, y, self._z_source)
        return beta_x, beta_y

    def arrival_time(self, theta_x, theta_y, kwargs_lens, k=None):
        """
        light travel time relative to a straight path through the coordinate (0,0)
        Negative sign means earlier arrival time

        :param theta_x: angle in x-direction on the image
        :param theta_y: angle in y-direction on the image
        :param kwargs_lens:
        :return: travel time in unit of days
        """
        dt_grav = np.zeros_like(theta_x)
        dt_geo = np.zeros_like(theta_x)
        x = np.zeros_like(theta_x)
        y = np.zeros_like(theta_y)
        z_before = 0
        alpha_x = theta_x
        alpha_y = theta_y
        for idex in self._sorted_redshift_index:
            z_lens = self._redshift_list[idex]
            delta_T = self._cosmo_bkg.T_xy(z_before, z_lens)
            dt_geo_new = self._geometrical_delay(alpha_x, alpha_y, delta_T)
            x, y = self._ray_step(x, y, alpha_x, alpha_y, delta_T)
            dt_grav_new = self._gravitational_delay(x, y, kwargs_lens, idex, z_lens)
            alpha_x, alpha_y = self._add_deflection(x, y, alpha_x, alpha_y, kwargs_lens, idex, z_lens)
            dt_geo = dt_geo + dt_geo_new
            dt_grav = dt_grav + dt_grav_new
            z_before = z_lens
        delta_T = self._cosmo_bkg.T_xy(z_before, self._z_source)
        dt_geo += self._geometrical_delay(alpha_x, alpha_y, delta_T)
        return dt_grav + dt_geo

    def alpha(self, theta_x, theta_y, kwargs_lens, k=None):
        """
        reduced deflection angle

        :param theta_x: angle in x-direction
        :param theta_y: angle in y-direction
        :param kwargs_lens: lens model kwargs
        :return:
        """
        beta_x, beta_y = self.ray_shooting(theta_x, theta_y, kwargs_lens)
        alpha_x = theta_x - beta_x
        alpha_y = theta_y - beta_y
        return alpha_x, alpha_y

    def hessian(self, theta_x, theta_y, kwargs_lens, k=None, diff=0.000001):
        """
        computes the hessian components f_xx, f_yy, f_xy from f_x and f_y with numerical differentiation

        :param theta_x: x-position (preferentially arcsec)
        :type theta_x: numpy array
        :param theta_y: y-position (preferentially arcsec)
        :type theta_y: numpy array
        :param kwargs_lens: list of keyword arguments of lens model parameters matching the lens model classes
        :param diff: numerical differential step (float)
        :return: f_xx, f_xy, f_yx, f_yy
        """

        alpha_ra, alpha_dec = self.alpha(theta_x, theta_y, kwargs_lens)

        alpha_ra_dx, alpha_dec_dx = self.alpha(theta_x + diff, theta_y, kwargs_lens)
        alpha_ra_dy, alpha_dec_dy = self.alpha(theta_x, theta_y + diff, kwargs_lens)

        dalpha_rara = (alpha_ra_dx - alpha_ra)/diff
        dalpha_radec = (alpha_ra_dy - alpha_ra)/diff
        dalpha_decra = (alpha_dec_dx - alpha_dec)/diff
        dalpha_decdec = (alpha_dec_dy - alpha_dec)/diff

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

    def _index_ordering(self, redshift_list):
        """

        :param redshift_list: list of redshifts
        :return: indexes in acending order to be evaluated (from z=0 to z=z_source)
        """
        redshift_list = np.array(redshift_list)
        sort_index = np.argsort(redshift_list[redshift_list < self._z_source])
        if len(sort_index) < 1:
            raise ValueError("There is no lens object between observer at z=0 and source at z=%s" % self._z_source)
        return sort_index

    def _reduced2physical_deflection(self, alpha_reduced, z_lens, z_source):
        """
        alpha_reduced = D_ds/Ds alpha_physical

        :param alpha_reduced: reduced deflection angle
        :param z_lens: lens redshift
        :param z_source: source redshift
        :return: physical deflection angle
        """
        factor = self._cosmo_bkg.D_xy(0, z_source) / self._cosmo_bkg.D_xy(z_lens, z_source)
        return alpha_reduced * factor

    def _gravitational_delay(self, x, y, kwargs_lens, idex, z_lens):
        """

        :param x: co-moving coordinate at the lens plane
        :param y: co-moving coordinate at the lens plane
        :param kwargs_lens: lens model keyword arguments
        :param z_lens: redshift of the deflector
        :param idex: index of the lens model
        :return: gravitational delay in units of days as seen at z=0
        """
        theta_x, theta_y = self._co_moving2angle(x, y, z_lens)
        potential = self._lens_model.potential(theta_x, theta_y, kwargs_lens, k=idex)
        delay_days = self._lensing_potential2time_delay(potential, z_lens, z_source=self._z_source)
        return -delay_days

    def _geometrical_delay(self, alpha_x, alpha_y, delta_T):
        """
        geometrical delay (evaluated at z=0) of a light ray with an angle relative to the shortest path

        :param alpha_x: angle relative to a straight path
        :param alpha_y: angle relative to a straight path
        :param delta_T: transversal diameter distance between the start and end of the ray
        :return: geometrical delay in units of days
        """
        dt_days = (alpha_x**2 + alpha_y**2) / 2. * delta_T * const.Mpc / const.c / const.day_s * const.arcsec**2
        return dt_days

    def _lensing_potential2time_delay(self, potential, z_lens, z_source):
        """
        transforms the lensing potential (in units arcsec^2) to a gravitational time-delay as measured at z=0

        :param potential: lensing potential
        :param z_lens: redshift of the deflector
        :param z_source: redshift of source for the definition of the lensing quantities
        :return: gravitational time-delay in units of days
        """
        D_dt = self._cosmo_bkg.D_dt(z_lens, z_source)
        delay_days = const.delay_arcsec2days(potential, D_dt)
        return delay_days

    def _co_moving2angle(self, x, y, z_lens):
        """
        transforms co-moving distances Mpc into angles on the sky (radian)

        :param x: co-moving distance
        :param y: co-moving distance
        :param z_lens: redshift of plane
        :return: angles on the sky
        """
        T_z = self._cosmo_bkg.T_xy(0, z_lens)
        theta_x = x / T_z
        theta_y = y / T_z
        return theta_x, theta_y

    def _ray_step(self, x, y, alpha_x, alpha_y, delta_T):
        """
        ray propagation with small angle approximation

        :param x: co-moving x-position
        :param y: co-moving y-position
        :param alpha_x: deflection angle in x-direction at (x, y)
        :param alpha_y: deflection angle in y-direction at (x, y)
        :param delta_T: transversal angular diameter distance to the next step
        :return:
        """
        x_ = x + alpha_x * delta_T
        y_ = y + alpha_y * delta_T
        return x_, y_

    def _add_deflection(self, x, y, alpha_x, alpha_y, kwargs_lens, idex, z_lens):
        """
        adds the pyhsical deflection angle of a single lens plane to the deflection field

        :param x: co-moving distance at the deflector plane
        :param y: co-moving distance at the deflector plane
        :param alpha_x: physical angle (radian) before the deflector plane
        :param alpha_y: physical angle (radian) before the deflector plane
        :param kwargs_lens: lens model parameter kwargs
        :param idex: index of the lens model to be added
        :param z_lens: redshift of the deflector plane
        :return: updated physical deflection after deflector plane (in a backwards ray-tracing perspective)
        """
        theta_x, theta_y = self._co_moving2angle(x, y, z_lens)
        alpha_x_red, alpha_y_red = self._lens_model.alpha(theta_x, theta_y, kwargs_lens, k=idex)
        alpha_x_phys = self._reduced2physical_deflection(alpha_x_red, z_lens, z_source=self._z_source)
        alpha_y_phys = self._reduced2physical_deflection(alpha_y_red, z_lens, z_source=self._z_source)
        alpha_x_new = alpha_x - alpha_x_phys
        alpha_y_new = alpha_y - alpha_y_phys
        return alpha_x_new, alpha_y_new
예제 #28
0
class TestLensModel(object):
    """
    tests the source model routines
    """
    def setup(self):
        self.lensModel = SinglePlane(['GAUSSIAN'])
        self.kwargs = [{
            'amp': 1.,
            'sigma_x': 2.,
            'sigma_y': 2.,
            'center_x': 0.,
            'center_y': 0.
        }]

    def test_potential(self):
        output = self.lensModel.potential(x=1., y=1., kwargs=self.kwargs)
        assert output == 0.77880078307140488 / (8 * np.pi)

    def test_alpha(self):
        output1, output2 = self.lensModel.alpha(x=1., y=1., kwargs=self.kwargs)
        assert output1 == -0.19470019576785122 / (8 * np.pi)
        assert output2 == -0.19470019576785122 / (8 * np.pi)

    def test_ray_shooting(self):
        delta_x, delta_y = self.lensModel.ray_shooting(x=1.,
                                                       y=1.,
                                                       kwargs=self.kwargs)
        assert delta_x == 1 + 0.19470019576785122 / (8 * np.pi)
        assert delta_y == 1 + 0.19470019576785122 / (8 * np.pi)

    def test_mass_2d(self):
        lensModel = SinglePlane(['GAUSSIAN_KAPPA'])
        kwargs = [{'amp': 1., 'sigma': 2., 'center_x': 0., 'center_y': 0.}]
        output = lensModel.mass_2d(r=1, kwargs=kwargs)
        assert output == 0.11750309741540453

    def test_bool_list(self):
        lensModel = SinglePlane(['SPEMD', 'SHEAR'])
        kwargs = [{
            'theta_E': 1,
            'gamma': 1,
            'e1': 0.1,
            'e2': -0.1,
            'center_x': 0,
            'center_y': 0
        }, {
            'e1': 0.01,
            'e2': -0.02
        }]
        alphax_1, alphay_1 = lensModel.alpha(1, 1, kwargs, k=0)
        alphax_1_list, alphay_1_list = lensModel.alpha(1, 1, kwargs, k=[0])
        npt.assert_almost_equal(alphax_1, alphax_1_list, decimal=5)
        npt.assert_almost_equal(alphay_1, alphay_1_list, decimal=5)

        alphax_1_1, alphay_1_1 = lensModel.alpha(1, 1, kwargs, k=0)
        alphax_1_2, alphay_1_2 = lensModel.alpha(1, 1, kwargs, k=1)
        alphax_full, alphay_full = lensModel.alpha(1, 1, kwargs, k=None)
        npt.assert_almost_equal(alphax_1_1 + alphax_1_2,
                                alphax_full,
                                decimal=5)
        npt.assert_almost_equal(alphay_1_1 + alphay_1_2,
                                alphay_full,
                                decimal=5)

    def test_init(self):
        lens_model_list = ['TNFW', 'SPEMD_SMOOTH']
        lensModel = SinglePlane(lens_model_list=lens_model_list)
        assert lensModel.func_list[0].param_names[0] == 'Rs'
예제 #29
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
예제 #30
0
 def test_mass_2d(self):
     lensModel = SinglePlane(['GAUSSIAN_KAPPA'])
     output = lensModel.mass_2d(r=1, kwargs=self.kwargs)
     assert output == 0.11750309741540453