def __init__(self, lensModel, sourceModel):
        """

        :param lensModel: lenstronomy LensModel() class instance
        :param sourceModel: LightModel () class instance
        The lightModel includes:
        - source_scale_factor_list: list of floats corresponding to the rescaled deflection angles to the specific source
         components. None indicates that the list will be set to 1, meaning a single source plane model (in single lens plane mode).
        - source_redshift_list: list of redshifts of the light components (in multi lens plane mode)
        """
        self._lightModel = sourceModel
        self._lensModel = lensModel
        light_model_list = sourceModel.profile_type_list
        self._multi_lens_plane = lensModel.multi_plane
        self._source_redshift_list = sourceModel.redshift_list
        self._deflection_scaling_list = sourceModel.deflection_scaling_list
        self._multi_source_plane = True
        if self._multi_lens_plane is True:
            self._bkg_cosmo = Background(lensModel.cosmo)
            if self._source_redshift_list is None:
                self._multi_source_plane = False
            elif len(self._source_redshift_list) != len(light_model_list):
                raise ValueError("length of redshift_list must correspond to length of light_model_list")
            elif np.max(self._source_redshift_list) > self._lensModel.z_source:
                raise ValueError("redshift of source_redshift_list have to be smaler or equal to the one specified in the lens model.")
            else:
                self._sorted_source_redshift_index = self._index_ordering(self._source_redshift_list)
        else:
            if self._deflection_scaling_list is None:
                self._multi_source_plane = False
            elif len(self._deflection_scaling_list) != len(light_model_list):
                raise ValueError('length of scale_factor_list must correspond to length of light_model_list!')
    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
Example #3
0
    def __init__(self, lensModel, sourceModel):
        """

        :param lensModel: LensModel() class instance
        :param sourceModel: LightModel() class instance.

         The lightModel includes:

         - source_scale_factor_list: list of floats corresponding to the rescaled deflection angles to the specific source components. None indicates that the list will be set to 1, meaning a single source plane model (in single lens plane mode).
         - source_redshift_list: list of redshifts of the light components (in multi lens plane mode)
        """
        self._lightModel = sourceModel
        self._lensModel = lensModel
        light_model_list = sourceModel.profile_type_list
        self._multi_lens_plane = lensModel.multi_plane
        self._source_redshift_list = sourceModel.redshift_list
        self._deflection_scaling_list = sourceModel.deflection_scaling_list
        self._multi_source_plane = True
        if self._multi_lens_plane is True:
            if self._deflection_scaling_list is not None:
                raise ValueError(
                    'deflection scaling for different source planes not possible in combination of '
                    'multi-lens plane modeling. You have to specify the redshifts of the sources instead.'
                )
            self._bkg_cosmo = Background(lensModel.cosmo)
            if self._source_redshift_list is None:
                self._multi_source_plane = False
            elif len(self._source_redshift_list) != len(light_model_list):
                raise ValueError(
                    "length of redshift_list must correspond to length of light_model_list"
                )
            elif np.max(self._source_redshift_list) > self._lensModel.z_source:
                raise ValueError(
                    "redshift of source_redshift_list have to be smaler or equal to the one specified in "
                    "the lens model.")
            else:
                self._sorted_source_redshift_index = self._index_ordering(
                    self._source_redshift_list)
                self._T0z_list = []
                for z_stop in self._source_redshift_list:
                    self._T0z_list.append(self._bkg_cosmo.T_xy(0, z_stop))
                z_start = 0
                self._T_ij_start_list = []
                self._T_ij_end_list = []
                for i, index_source in enumerate(
                        self._sorted_source_redshift_index):
                    z_stop = self._source_redshift_list[index_source]
                    T_ij_start, T_ij_end = self._lensModel.lens_model.transverse_distance_start_stop(
                        z_start, z_stop, include_z_start=False)
                    self._T_ij_start_list.append(T_ij_start)
                    self._T_ij_end_list.append(T_ij_end)
                    z_start = z_stop
        else:
            if self._deflection_scaling_list is None:
                self._multi_source_plane = False
            elif len(self._deflection_scaling_list) != len(light_model_list):
                raise ValueError(
                    'length of scale_factor_list must correspond to length of light_model_list!'
                )
Example #4
0
    def __init__(self, z_lens, z_source, cosmo=None):
        """

        :param z_lens: redshift of lens
        :param z_source: redshift of source
        :param cosmo: astropy.cosmology instance
        """

        self.z_lens = z_lens
        self.z_source = z_source
        self.background = Background(cosmo=cosmo)
Example #5
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"
            )
Example #6
0
class TestCosmoProp(object):
    def setup(self):
        self.z_L = 0.8
        self.z_S = 3.0
        from astropy.cosmology import FlatLambdaCDM
        cosmo = FlatLambdaCDM(H0=70, Om0=0.3, Ob0=0.05)
        self.bkg = Background(cosmo=cosmo)

    def test_scale_factor(self):
        z = 0.7
        assert self.bkg.a_z(z) == 1. / (1 + z)

    def test_rho_crit(self):
        assert self.bkg.rho_crit == 135955133951.10692

    def test_interpol(self):
        from astropy.cosmology import FlatLambdaCDM
        cosmo = FlatLambdaCDM(H0=70, Om0=0.3, Ob0=0.05)
        bkg = Background(cosmo=cosmo)

        bkg_interp = Background(cosmo=cosmo,
                                interp=True,
                                num_interp=100,
                                z_stop=10)
        d_xy = bkg.d_xy(z_observer=0.1, z_source=0.8)
        d_xy_interp = bkg_interp.d_xy(z_observer=0.1, z_source=0.8)
        npt.assert_almost_equal(d_xy_interp / d_xy, 1, decimal=5)
Example #7
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)
Example #8
0
class TestCosmoProp(object):
    def setup(self):
        self.z_L = 0.8
        self.z_S = 3.0
        from astropy.cosmology import FlatLambdaCDM
        cosmo = FlatLambdaCDM(H0=70, Om0=0.3, Ob0=0.05)
        self.bkg = Background(cosmo=cosmo)

    def test_scale_factor(self):
        z = 0.7
        assert self.bkg.a_z(z) == 1. / (1 + z)

    def test_rho_crit(self):
        assert self.bkg.rho_crit == 135955133951.10692
Example #9
0
    def test_interpol(self):
        from astropy.cosmology import FlatLambdaCDM
        cosmo = FlatLambdaCDM(H0=70, Om0=0.3, Ob0=0.05)
        bkg = Background(cosmo=cosmo)

        bkg_interp = Background(cosmo=cosmo,
                                interp=True,
                                num_interp=100,
                                z_stop=10)
        d_xy = bkg.d_xy(z_observer=0.1, z_source=0.8)
        d_xy_interp = bkg_interp.d_xy(z_observer=0.1, z_source=0.8)
        npt.assert_almost_equal(d_xy_interp / d_xy, 1, decimal=5)
Example #10
0
    def __init__(self,
                 lens_model_list,
                 lens_redshift_list,
                 z_source_convention,
                 cosmo=None,
                 numerical_alpha_class=None,
                 cosmo_interp=False,
                 z_interp_stop=None,
                 num_z_interp=100,
                 kwargs_interp=None):
        """
        A description of the recursive multi-plane formalism can be found e.g. here: https://arxiv.org/abs/1312.1536

        :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)
        :param kwargs_interp: interpolation keyword arguments specifying the numerics.
         See description in the Interpolate() class. Only applicable for 'INTERPOL' and 'INTERPOL_SCALED' models.

        """
        if z_interp_stop is None:
            z_interp_stop = z_source_convention
        self._cosmo_bkg = Background(cosmo,
                                     interp=cosmo_interp,
                                     z_stop=z_interp_stop,
                                     num_interp=num_z_interp)
        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_redshift_list = lens_redshift_list
        super(MultiPlaneBase,
              self).__init__(lens_model_list,
                             numerical_alpha_class=numerical_alpha_class,
                             lens_redshift_list=lens_redshift_list,
                             z_source_convention=z_source_convention,
                             kwargs_interp=kwargs_interp)

        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 = []
        # Sort redshift for vectorized reduced2physical factor calculation
        if len(lens_model_list) < 1:
            self._reduced2physical_factor = []
        else:
            z_sort = np.array(
                self._lens_redshift_list)[self._sorted_redshift_index]
            z_source_array = np.ones(z_sort.shape) * z_source_convention
            self._reduced2physical_factor = self._cosmo_bkg.d_xy(
                0, z_source_convention) / self._cosmo_bkg.d_xy(
                    z_sort, z_source_array)
        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)
            z_before = z_lens
class MultiPlaneBase(ProfileListBase):
    """
    Multi-plane lensing class

    The lens model deflection angles are in units of reduced deflections from the specified redshift of the lens to the
    sourde redshift of the class instance.
    """
    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_redshift_list = lens_redshift_list
        super(MultiPlaneBase,
              self).__init__(lens_model_list,
                             numerical_alpha_class=numerical_alpha_class,
                             lens_redshift_list=lens_redshift_list,
                             z_source_convention=z_source_convention)

        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

    def ray_shooting_partial(self,
                             x,
                             y,
                             alpha_x,
                             alpha_y,
                             z_start,
                             z_stop,
                             kwargs_lens,
                             include_z_start=False,
                             T_ij_start=None,
                             T_ij_end=None):
        """
        ray-tracing through parts of the coin, starting with (x,y) co-moving distances and angles (alpha_x, alpha_y) at redshift z_start
        and then backwards to redshift 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 include_z_start: bool, if True, includes the computation of the deflection angle at the same redshift as
         the start of the ray-tracing. ATTENTION: deflection angles at the same redshift as z_stop will be computed always!
         This can lead to duplications in the computation of deflection angles.
        :param T_ij_start: transverse angular distance between the starting redshift to the first lens plane to follow.
         If not set, will compute the distance each time this function gets executed.
        :param T_ij_end: transverse angular distance between the last lens plane being computed and z_end.
         If not set, will compute the distance each time this function gets executed.
        :return: co-moving position and angles at redshift z_stop
        """
        x = np.array(x, dtype=float)
        y = np.array(y, dtype=float)
        alpha_x = np.array(alpha_x)
        alpha_y = np.array(alpha_y)
        z_lens_last = z_start
        first_deflector = True

        for i, idex in enumerate(self._sorted_redshift_index):
            z_lens = self._lens_redshift_list[idex]

            if self._start_condition(include_z_start, z_lens,
                                     z_start) and z_lens <= z_stop:
                if first_deflector is True:
                    if T_ij_start is None:
                        if z_start == 0:
                            delta_T = self._T_ij_list[0]
                        else:
                            delta_T = self._cosmo_bkg.T_xy(z_start, z_lens)
                    else:
                        delta_T = T_ij_start
                    first_deflector = False
                else:
                    delta_T = self._T_ij_list[i]
                x, y = self._ray_step_add(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 T_ij_end is None:
            if z_lens_last == z_stop:
                delta_T = 0
            else:
                delta_T = self._cosmo_bkg.T_xy(z_lens_last, z_stop)
        else:
            delta_T = T_ij_end
        x, y = self._ray_step_add(x, y, alpha_x, alpha_y, delta_T)
        return x, y, alpha_x, alpha_y

    def transverse_distance_start_stop(self,
                                       z_start,
                                       z_stop,
                                       include_z_start=False):
        """
        computes the transverse distance (T_ij) that is required by the ray-tracing between the starting redshift and
        the first deflector afterwards and the last deflector before the end of the ray-tracing.

        :param z_start: redshift of the start of the ray-tracing
        :param z_stop: stop of ray-tracing
        :return: T_ij_start, T_ij_end
        """
        z_lens_last = z_start
        first_deflector = True
        T_ij_start = None
        for i, idex in enumerate(self._sorted_redshift_index):
            z_lens = self._lens_redshift_list[idex]
            if self._start_condition(include_z_start, z_lens,
                                     z_start) and z_lens <= z_stop:
                if first_deflector is True:
                    T_ij_start = self._cosmo_bkg.T_xy(z_start, z_lens)
                    first_deflector = False
                z_lens_last = z_lens
        T_ij_end = self._cosmo_bkg.T_xy(z_lens_last, z_stop)
        return T_ij_start, T_ij_end

    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 redshift 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
        :param check_convention: flag to check the image position convention (leave this alone)
        :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._lens_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_add(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_add(x, y, alpha_x, alpha_y, delta_T)

        pos_x.append(x)
        pos_y.append(y)
        redshifts.append(z_stop)
        T_z_source = self._cosmo_bkg.T_xy(0, z_stop)
        Tz_list.append(T_z_source)

        return pos_x, pos_y, redshifts, Tz_list

    def geo_shapiro_delay(self,
                          theta_x,
                          theta_y,
                          kwargs_lens,
                          z_stop,
                          T_z_stop=None,
                          T_ij_end=None):
        """
        geometric and Shapiro (gravitational) 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: lens model keyword argument list
        :param z_stop: redshift of the source to stop the backwards ray-tracing
        :param T_z_stop: optional, transversal angular distance from z=0 to z_stop
        :param T_ij_end: optional, transversal angular distance between the last lensing plane and the source plane
        :return: dt_geo, dt_shapiro, [days]
        """
        dt_grav = np.zeros_like(theta_x, dtype=float)
        dt_geo = np.zeros_like(theta_x, dtype=float)
        x = np.zeros_like(theta_x, dtype=float)
        y = np.zeros_like(theta_y, dtype=float)
        alpha_x = np.array(theta_x, dtype=float)
        alpha_y = np.array(theta_y, dtype=float)
        i = 0
        z_lens_last = 0
        for i, index in enumerate(self._sorted_redshift_index):
            z_lens = self._lens_redshift_list[index]
            if z_lens <= z_stop:
                T_ij = self._T_ij_list[i]
                x_new, y_new = self._ray_step(x, y, alpha_x, alpha_y, T_ij)
                if i == 0:
                    pass
                elif T_ij > 0:
                    T_j = self._T_z_list[i]
                    T_i = self._T_z_list[i - 1]
                    beta_i_x, beta_i_y = x / T_i, y / T_i
                    beta_j_x, beta_j_y = x_new / T_j, y_new / T_j
                    dt_geo_new = self._geometrical_delay(
                        beta_i_x, beta_i_y, beta_j_x, beta_j_y, T_i, T_j, T_ij)
                    dt_geo += dt_geo_new
                x, y = x_new, y_new
                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_grav += dt_grav_new
                z_lens_last = z_lens
        if T_ij_end is None:
            T_ij_end = self._cosmo_bkg.T_xy(z_lens_last, z_stop)
        T_ij = T_ij_end
        x_new, y_new = self._ray_step(x, y, alpha_x, alpha_y, T_ij)
        if T_z_stop is None:
            T_z_stop = self._cosmo_bkg.T_xy(0, z_stop)
        T_j = T_z_stop
        T_i = self._T_z_list[i]
        beta_i_x, beta_i_y = x / T_i, y / T_i
        beta_j_x, beta_j_y = x_new / T_j, y_new / T_j
        dt_geo_new = self._geometrical_delay(beta_i_x, beta_i_y, beta_j_x,
                                             beta_j_y, T_i, T_j, T_ij)
        dt_geo += dt_geo_new
        return dt_geo, dt_grav

    @staticmethod
    def _index_ordering(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 < z_source])
        sort_index = np.argsort(redshift_list)
        #if len(sort_index) < 1:
        #    Warning("There is no lens object between observer at z=0 and source at z=%s" % z_source)
        return sort_index

    def _reduced2physical_deflection(self, alpha_reduced, index_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[index_lens]
        return alpha_reduced * factor

    def _gravitational_delay(self, x, y, kwargs_lens, index, 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 index: index of the lens model in sorted redshfit convention
        :return: gravitational delay in units of days as seen at z=0
        """
        theta_x, theta_y = self._co_moving2angle(x, y, index)
        k = self._sorted_redshift_index[index]
        potential = self.func_list[k].function(theta_x, theta_y,
                                               **kwargs_lens[k])
        delay_days = self._lensing_potential2time_delay(
            potential, z_lens, z_source=self._z_source_convention)
        return -delay_days

    @staticmethod
    def _geometrical_delay(beta_i_x, beta_i_y, beta_j_x, beta_j_y, T_i, T_j,
                           T_ij):
        """

        :param beta_i_x: angle on the sky at plane i
        :param beta_i_y: angle on the sky at plane i
        :param beta_j_x: angle on the sky at plane j
        :param beta_j_y: angle on the sky at plane j
        :param T_i: transverse diameter distance to z_i
        :param T_j: transverse diameter distance to z_j
        :param T_ij: transverse diameter distance from z_i to z_j
        :return: excess delay relative to a straight line
        """
        d_beta_x = beta_j_x - beta_i_x
        d_beta_y = beta_j_y - beta_i_y
        tau_ij = T_i * T_j / T_ij * const.Mpc / const.c / const.day_s * const.arcsec**2
        return tau_ij * (d_beta_x**2 + d_beta_y**2) / 2

    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.ddt(z_lens, z_source)
        delay_days = const.delay_arcsec2days(potential, D_dt)
        return delay_days

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

        :param x: co-moving distance
        :param y: co-moving distance
        :param index: index of plane
        :return: angles on the sky
        """
        T_z = self._T_z_list[index]
        theta_x = x / T_z
        theta_y = y / T_z
        return theta_x, theta_y

    @staticmethod
    def _ray_step(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: transverse angular diameter distance to the next step
        :return: co-moving position at the next step (backwards)
        """
        x_ = x + alpha_x * delta_T
        y_ = y + alpha_y * delta_T
        return x_, y_

    @staticmethod
    def _ray_step_add(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: transverse angular diameter distance to the next step
        :return: co-moving position at the next step (backwards)
        """
        x += alpha_x * delta_T
        y += alpha_y * delta_T
        return x, y

    def _add_deflection(self, x, y, alpha_x, alpha_y, kwargs_lens, index):
        """
        adds the physical 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 index: index of the lens model to be added in sorted redshift list convention
        :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, index)
        k = self._sorted_redshift_index[index]
        alpha_x_red, alpha_y_red = self.func_list[k].derivatives(
            theta_x, theta_y, **kwargs_lens[k])
        alpha_x_phys = self._reduced2physical_deflection(alpha_x_red, index)
        alpha_y_phys = self._reduced2physical_deflection(alpha_y_red, index)
        return alpha_x - alpha_x_phys, alpha_y - alpha_y_phys

    @staticmethod
    def _start_condition(inclusive, z_lens, z_start):

        if inclusive:
            return z_lens >= z_start
        else:
            return z_lens > z_start
Example #12
0
class Image2SourceMapping(object):
    """
    this class handles multiple source planes and performs the computation of predicted surface brightness at given
    image positions.
    The class is enable to deal with an arbitrary number of different source planes. There are two different settings:

    Single lens plane modelling:
    In case of a single deflector, lenstronomy models the reduced deflection angles
    (matched to the source plane in single source plane mode). Each source light model can be added a number
    (scale_factor) that rescales the reduced deflection angle to the specific source plane.

    Multiple lens plane modelling:
    The multi-plane lens modelling requires the assumption of a cosmology and the redshifts of the multiple lens and
    source planes. The backwards ray-tracing is performed and stopped at the different source plane redshift to compute
    the mapping between source to image plane.
    """

    def __init__(self, lensModel, sourceModel):
        """

        :param lensModel: lenstronomy LensModel() class instance
        :param sourceModel: LightModel () class instance
        The lightModel includes:
        - source_scale_factor_list: list of floats corresponding to the rescaled deflection angles to the specific source
         components. None indicates that the list will be set to 1, meaning a single source plane model (in single lens plane mode).
        - source_redshift_list: list of redshifts of the light components (in multi lens plane mode)
        """
        self._lightModel = sourceModel
        self._lensModel = lensModel
        light_model_list = sourceModel.profile_type_list
        self._multi_lens_plane = lensModel.multi_plane
        self._source_redshift_list = sourceModel.redshift_list
        self._deflection_scaling_list = sourceModel.deflection_scaling_list
        self._multi_source_plane = True
        if self._multi_lens_plane is True:
            if self._deflection_scaling_list is not None:
                raise ValueError('deflection scaling for different source planes not possible in combination of '
                                 'multi-lens plane modeling. You have to specify the redshifts of the sources instead.')
            self._bkg_cosmo = Background(lensModel.cosmo)
            if self._source_redshift_list is None:
                self._multi_source_plane = False
            elif len(self._source_redshift_list) != len(light_model_list):
                raise ValueError("length of redshift_list must correspond to length of light_model_list")
            elif np.max(self._source_redshift_list) > self._lensModel.z_source:
                raise ValueError("redshift of source_redshift_list have to be smaler or equal to the one specified in "
                                 "the lens model.")
            else:
                self._sorted_source_redshift_index = self._index_ordering(self._source_redshift_list)
                self._T0z_list = []
                for z_stop in self._source_redshift_list:
                    self._T0z_list.append(self._bkg_cosmo.T_xy(0, z_stop))
                z_start = 0
                self._T_ij_start_list = []
                self._T_ij_end_list = []
                for i, index_source in enumerate(self._sorted_source_redshift_index):
                    z_stop = self._source_redshift_list[index_source]
                    T_ij_start, T_ij_end = self._lensModel.lens_model.transverse_distance_start_stop(z_start, z_stop, include_z_start=False)
                    self._T_ij_start_list.append(T_ij_start)
                    self._T_ij_end_list.append(T_ij_end)
                    z_start = z_stop
        else:
            if self._deflection_scaling_list is None:
                self._multi_source_plane = False
            elif len(self._deflection_scaling_list) != len(light_model_list):
                raise ValueError('length of scale_factor_list must correspond to length of light_model_list!')

    def image2source(self, x, y, kwargs_lens, index_source):
        """
        mapping of image plane to source plane coordinates
        WARNING: for multi lens plane computations and multi source planes, this computation can be slow and should be
        used as rarely as possible.

        :param x: image plane coordinate (angle)
        :param y: image plane coordinate (angle)
        :param kwargs_lens: lens model kwargs list
        :param index_source: int, index of source model
        :return: source plane coordinate corresponding to the source model of index idex_source
        """
        if self._multi_source_plane is False:
            x_source, y_source = self._lensModel.ray_shooting(x, y, kwargs_lens)
        else:
            if self._multi_lens_plane is False:
                x_alpha, y_alpha = self._lensModel.alpha(x, y, kwargs_lens)
                scale_factor = self._deflection_scaling_list[index_source]
                x_source = x - x_alpha * scale_factor
                y_source = y - y_alpha * scale_factor
            else:
                z_stop = self._source_redshift_list[index_source]
                x_ = np.zeros_like(x)
                y_ = np.zeros_like(y)
                x_comov, y_comov, alpha_x, alpha_y = self._lensModel.lens_model.ray_shooting_partial(x_, y_, x, y,
                                                                                                     0, z_stop,
                                                                                                     kwargs_lens,
                                                                                                     include_z_start=False)

                T_z = self._T0z_list[index_source]
                x_source = x_comov / T_z
                y_source = y_comov / T_z
        return x_source, y_source

    def image_flux_joint(self, x, y, kwargs_lens, kwargs_source, k=None):
        """

        :param x: coordinate in image plane
        :param y: coordinate in image plane
        :param kwargs_lens: lens model kwargs list
        :param kwargs_source: source model kwargs list
        :return: surface brightness of all joint light components at image position (x, y)
        """
        if self._multi_source_plane is False:
            x_source, y_source = self._lensModel.ray_shooting(x, y, kwargs_lens)
            return self._lightModel.surface_brightness(x_source, y_source, kwargs_source, k=k)
        else:
            flux = np.zeros_like(x)
            if self._multi_lens_plane is False:
                x_alpha, y_alpha = self._lensModel.alpha(x, y, kwargs_lens)
                for i in range(len(self._deflection_scaling_list)):
                    scale_factor = self._deflection_scaling_list[i]
                    x_source = x - x_alpha * scale_factor
                    y_source = y - y_alpha * scale_factor
                    if k is None or k ==i:
                        flux += self._lightModel.surface_brightness(x_source, y_source, kwargs_source, k=i)
            else:
                x_comov = np.zeros_like(x)
                y_comov = np.zeros_like(y)
                alpha_x, alpha_y = x, y
                x_source, y_source = alpha_x, alpha_y
                z_start = 0
                for i, index_source in enumerate(self._sorted_source_redshift_index):
                    z_stop = self._source_redshift_list[index_source]
                    if z_stop > z_start:
                        T_ij_start = self._T_ij_start_list[i]
                        T_ij_end = self._T_ij_end_list[i]
                        x_comov, y_comov, alpha_x, alpha_y = self._lensModel.lens_model.ray_shooting_partial(x_comov, y_comov, alpha_x, alpha_y, z_start, z_stop,
                                                                        kwargs_lens, include_z_start=False,
                                                                        T_ij_start=T_ij_start, T_ij_end=T_ij_end)

                        T_z = self._T0z_list[index_source]
                        x_source = x_comov / T_z
                        y_source = y_comov / T_z
                    if k is None or k == i:
                        flux += self._lightModel.surface_brightness(x_source, y_source, kwargs_source, k=index_source)
                    z_start = z_stop
            return flux

    def image_flux_split(self, x, y, kwargs_lens, kwargs_source):
        """

        :param x: coordinate in image plane
        :param y: coordinate in image plane
        :param kwargs_lens: lens model kwargs list
        :param kwargs_source: source model kwargs list
        :return: list of responses of every single basis component with default amplitude amp=1, in the same order as the light_model_list
        """
        if self._multi_source_plane is False:
            x_source, y_source = self._lensModel.ray_shooting(x, y, kwargs_lens)
            return self._lightModel.functions_split(x_source, y_source, kwargs_source)
        else:
            response = []
            n = 0
            if self._multi_lens_plane is False:
                x_alpha, y_alpha = self._lensModel.alpha(x, y, kwargs_lens)
                for i in range(len(self._deflection_scaling_list)):
                    scale_factor = self._deflection_scaling_list[i]
                    x_source = x - x_alpha * scale_factor
                    y_source = y - y_alpha * scale_factor
                    response_i, n_i = self._lightModel.functions_split(x_source, y_source, kwargs_source, k=i)
                    response += response_i
                    n += n_i
            else:
                n_i_list = []
                x_comov = np.zeros_like(x)
                y_comov = np.zeros_like(y)
                alpha_x, alpha_y = x, y
                x_source, y_source = alpha_x, alpha_y
                z_start = 0
                for i, index_source in enumerate(self._sorted_source_redshift_index):
                    z_stop = self._source_redshift_list[index_source]
                    if z_stop > z_start:
                        T_ij_start = self._T_ij_start_list[i]
                        T_ij_end = self._T_ij_end_list[i]
                        x_comov, y_comov, alpha_x, alpha_y = self._lensModel.lens_model.ray_shooting_partial(x_comov,
                                                                y_comov, alpha_x, alpha_y, z_start, z_stop, kwargs_lens,
                                                                include_z_start=False, T_ij_start=T_ij_start,
                                                                T_ij_end=T_ij_end)
                        T_z = self._T0z_list[index_source]
                        x_source = x_comov / T_z
                        y_source = y_comov / T_z
                    response_i, n_i = self._lightModel.functions_split(x_source, y_source, kwargs_source, k=index_source)
                    n_i_list.append(n_i)
                    response += response_i
                    n += n_i
                    z_start = z_stop
                n_list = self._lightModel.num_param_linear_list(kwargs_source)
                response = self._re_order_split(response, n_list)

            return response, n

    @staticmethod
    def _index_ordering(redshift_list):
        """

        :param redshift_list: list of redshifts
        :return: indexes in ascending order to be evaluated (from z=0 to z=z_source)
        """
        redshift_list = np.array(redshift_list)
        sort_index = np.argsort(redshift_list)
        return sort_index

    def _re_order_split(self, response, n_list):
        """

        :param response: splitted functions in order of redshifts
        :param n_list: list of number of response vectors per model in order of the model list (not redshift ordered)
        :return: reshuffled array in order of the function definition
        """
        counter_regular = 0
        n_sum_list_regular = []

        for i in range(len(self._source_redshift_list)):
            n_sum_list_regular += [counter_regular]
            counter_regular += n_list[i]

        reshuffled = np.zeros_like(response)
        n_sum_sorted = 0
        for i, index in enumerate(self._sorted_source_redshift_index):
            n_i = n_list[index]
            n_sum = n_sum_list_regular[index]
            reshuffled[n_sum:n_sum + n_i] = response[n_sum_sorted:n_sum_sorted + n_i]
            n_sum_sorted += n_i
        return reshuffled
Example #13
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
Example #14
0
class LensCosmo(object):
    """
    class to manage the physical units and distances present in a single plane lens with fixed input cosmology
    """
    def __init__(self, z_lens, z_source, cosmo=None):
        """

        :param z_lens: redshift of lens
        :param z_source: redshift of source
        :param cosmo: astropy.cosmology instance
        """

        self.z_lens = z_lens
        self.z_source = z_source
        self.background = Background(cosmo=cosmo)

    @property
    def D_d(self):
        """

        :return: angular diameter distance to the deflector [Mpc]
        """
        return self.background.D_xy(0, self.z_lens)

    @property
    def D_s(self):
        """

        :return: angular diameter distance to the source [Mpc]
        """
        return self.background.D_xy(0, self.z_source)

    @property
    def D_ds(self):
        """

        :return: angular diameter distance from deflector to source [Mpc]
        """
        return self.background.D_xy(self.z_lens, self.z_source)

    @property
    def D_dt(self):
        """

        :return: time delay distance [Mpc]
        """
        return (1 + self.z_lens) * self.D_d * self.D_s / self.D_ds

    @property
    def epsilon_crit(self):
        """
        returns the critical projected mass density in units of M_sun/Mpc^2 (physical units)
        :return: critical projected mass density
        """
        if not hasattr(self, '_Epsilon_Crit'):
            const_SI = const.c ** 2 / (4 * np.pi * const.G)  #c^2/(4*pi*G) in units of [kg/m]
            conversion = const.Mpc / const.M_sun  # converts [kg/m] to [M_sun/Mpc]
            factor = const_SI*conversion   #c^2/(4*pi*G) in units of [M_sun/Mpc]
            self._Epsilon_Crit = self.D_s/(self.D_d*self.D_ds) * factor #[M_sun/Mpc^2]
        return self._Epsilon_Crit

    def phys2arcsec_lens(self, phys):
        """
        convert physical Mpc into arc seconds
        :param phys: physical distance [Mpc]
        :return: angular diameter [arcsec]
        """
        return phys / self.D_d/const.arcsec

    def arcsec2phys_lens(self, arcsec):
        """
        convert angular to physical quantities for lens plane
        :param arcsec: angular size at lens plane [arcsec]
        :return: physical size at lens plane [Mpc]
        """
        return arcsec * const.arcsec * self.D_d

    def arcsec2phys_source(self, arcsec):
        """
        convert angular to physical quantities for source plane
        :param arcsec: angular size at source plane [arcsec]
        :return: physical size at source plane [Mpc]
        """
        return arcsec * const.arcsec * self.D_s

    def kappa2proj_mass(self, kappa):
        """
        convert convergence to projected mass M_sun/Mpc^2
        :param kappa: lensing convergence
        :return: projected mass [M_sun/Mpc^2]
        """
        return kappa * self.epsilon_crit

    def mass_in_theta_E(self, theta_E):
        """
        mass within Einstein radius (area * epsilon crit) [M_sun]
        :param theta_E: Einstein radius [arcsec]
        :return: mass within Einstein radius [M_sun]
        """
        mass = self.arcsec2phys_lens(theta_E) ** 2 * np.pi * self.epsilon_crit
        return mass

    def mass_in_coin(self, theta_E):
        """

        :param theta_E: Einstein radius [arcsec]
        :return: mass in coin calculated in mean density of the universe
        """
        chi_L = self.background.T_xy(0, self.z_lens)
        chi_S = self.background.T_xy(0, self.z_source)
        return 1./3 * np.pi * (chi_L * theta_E * const.arcsec) ** 2 * chi_S * self.background.rho_crit  #[M_sun/Mpc**3]

    def time_delay_units(self, fermat_pot, kappa_ext=0):
        """

        :param delay_unitless: in units of arcsec^2 (e.g. Fermat potential)
        :param kappa_ext: unit-less
        :return: time delay in days
        """
        D_dt = self.D_dt / (1. - kappa_ext) * const.Mpc  # eqn 7 in Suyu et al.
        return D_dt / const.c * fermat_pot / const.day_s * const.arcsec ** 2  # * self.arcsec2phys_lens(1.)**2
Example #15
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
Example #16
0
    def test_foreground_shear(self):
        """
        scenario: a shear field in the foreground of the main deflector is placed
        we compute the expected shear on the lens plain and effectively model the same system in a single plane
        configuration
        We check for consistency of the two approaches and whether the specific redshift of the foreground shear field has
        an impact on the arrival time surface
        :return:
        """
        z_source = 1.5
        z_lens = 0.5
        z_shear = 0.2
        x, y = np.array([1., 0.]), np.array([0., 2.])
        from astropy.cosmology import default_cosmology
        from lenstronomy.Cosmo.background import Background

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

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

        fermat_pot = lensModel.fermat_potential(x, y, ra_source, dec_source,
                                                kwargs_lens_single)
        from lenstronomy.Cosmo.lens_cosmo import LensCosmo
        lensCosmo = LensCosmo(z_lens, z_source, cosmo=cosmo)
        Dt = lensCosmo.D_dt
        print(lensCosmo.D_dt)
        #t_simple = const.delay_arcsec2days(fermat_pot, Dt)
        t_simple = lensCosmo.time_delay_units(fermat_pot)
        dt_simple = t_simple[0] - t_simple[1]
        print(t_simple, t_multi)
        npt.assert_almost_equal(dt_simple / dt_multi, 1, decimal=2)
Example #17
0
 def setup(self):
     self.z_L = 0.8
     self.z_S = 3.0
     from astropy.cosmology import FlatLambdaCDM
     cosmo = FlatLambdaCDM(H0=70, Om0=0.3, Ob0=0.05)
     self.bkg = Background(cosmo=cosmo)
Example #18
0
class LensCosmo(object):
    """
    class to manage the physical units and distances present in a single plane lens with fixed input cosmology
    """
    def __init__(self, z_lens, z_source, cosmo=None):
        """

        :param z_lens: redshift of lens
        :param z_source: redshift of source
        :param cosmo: astropy.cosmology instance
        """

        self.z_lens = z_lens
        self.z_source = z_source
        self.background = Background(cosmo=cosmo)
        self.nfw_param = NFWParam()

    def a_z(self, z):
        """
        convert redshift into scale factor
        :param z: redshift
        :return: scale factor
        """
        return 1. / (1. + z)

    @property
    def h(self):
        return self.background.cosmo.H(0).value / 100.

    @property
    def D_d(self):
        """

        :return: angular diameter distance to the deflector [Mpc]
        """
        return self.background.D_xy(0, self.z_lens)

    @property
    def D_s(self):
        """

        :return: angular diameter distance to the source [Mpc]
        """
        return self.background.D_xy(0, self.z_source)

    @property
    def D_ds(self):
        """

        :return: angular diameter distance from deflector to source [Mpc]
        """
        return self.background.D_xy(self.z_lens, self.z_source)

    @property
    def D_dt(self):
        """

        :return: time delay distance [Mpc]
        """
        return (1 + self.z_lens) * self.D_d * self.D_s / self.D_ds

    @property
    def epsilon_crit(self):
        """
        returns the critical projected mass density in units of M_sun/Mpc^2 (physical units)
        :return: critical projected mass density
        """
        if not hasattr(self, '_Epsilon_Crit'):
            const_SI = const.c**2 / (4 * np.pi * const.G
                                     )  #c^2/(4*pi*G) in units of [kg/m]
            conversion = const.Mpc / const.M_sun  # converts [kg/m] to [M_sun/Mpc]
            factor = const_SI * conversion  #c^2/(4*pi*G) in units of [M_sun/Mpc]
            self._Epsilon_Crit = self.D_s / (
                self.D_d * self.D_ds) * factor  #[M_sun/Mpc^2]
        return self._Epsilon_Crit

    def phys2arcsec_lens(self, phys):
        """
        convert physical Mpc into arc seconds
        :param phys: physical distance [Mpc]
        :return: angular diameter [arcsec]
        """
        return phys / self.D_d / const.arcsec

    def arcsec2phys_lens(self, arcsec):
        """
        convert angular to physical quantities for lens plane
        :param arcsec: angular size at lens plane [arcsec]
        :return: physical size at lens plane [Mpc]
        """
        return arcsec * const.arcsec * self.D_d

    def arcsec2phys_source(self, arcsec):
        """
        convert angular to physical quantities for source plane
        :param arcsec: angular size at source plane [arcsec]
        :return: physical size at source plane [Mpc]
        """
        return arcsec * const.arcsec * self.D_s

    def kappa2proj_mass(self, kappa):
        """
        convert convergence to projected mass M_sun/Mpc^2
        :param kappa: lensing convergence
        :return: projected mass [M_sun/Mpc^2]
        """
        return kappa * self.epsilon_crit

    def mass_in_theta_E(self, theta_E):
        """
        mass within Einstein radius (area * epsilon crit) [M_sun]
        :param theta_E: Einstein radius [arcsec]
        :return: mass within Einstein radius [M_sun]
        """
        mass = self.arcsec2phys_lens(theta_E)**2 * np.pi * self.epsilon_crit
        return mass

    def mass_in_coin(self, theta_E):
        """

        :param theta_E: Einstein radius [arcsec]
        :return: mass in coin calculated in mean density of the universe
        """
        chi_L = self.background.T_xy(0, self.z_lens)
        chi_S = self.background.T_xy(0, self.z_source)
        return 1. / 3 * np.pi * (
            chi_L * theta_E * const.arcsec
        )**2 * chi_S * self.background.rho_crit  #[M_sun/Mpc**3]

    def time_delay_units(self, fermat_pot, kappa_ext=0):
        """

        :param fermat_pot: in units of arcsec^2 (e.g. Fermat potential)
        :param kappa_ext: unit-less
        :return: time delay in days
        """
        D_dt = self.D_dt / (1. - kappa_ext) * const.Mpc  # eqn 7 in Suyu et al.
        return D_dt / const.c * fermat_pot / const.day_s * const.arcsec**2  # * self.arcsec2phys_lens(1.)**2

    def time_delay2fermat_pot(self, dt):
        """

        :param dt: time delay in units of days
        :return: Fermat potential in units arcsec**2 for a given cosmology
        """
        D_dt = self.D_dt * const.Mpc
        return dt * const.c * const.day_s / D_dt / const.arcsec**2

    def nfw_angle2physical(self, Rs_angle, alpha_Rs):
        """
        converts the angular parameters into the physical ones for an NFW profile

        :param alpha_Rs: observed bending angle at the scale radius in units of arcsec
        :param Rs: scale radius in units of arcsec
        :return: M200, r200, Rs_physical, c
        """
        Rs = Rs_angle * const.arcsec * self.D_d
        theta_scaled = alpha_Rs * self.epsilon_crit * self.D_d * const.arcsec
        rho0 = theta_scaled / (4 * Rs**2 * (1 + np.log(1. / 2.)))
        rho0_com = rho0 / self.h**2 * self.a_z(self.z_lens)**3
        c = self.nfw_param.c_rho0(rho0_com)
        r200 = c * Rs
        M200 = self.nfw_param.M_r200(
            r200 * self.h / self.a_z(self.z_lens)) / self.h
        return rho0, Rs, c, r200, M200

    def nfw_physical2angle(self, M, c):
        """
        converts the physical mass and concentration parameter of an NFW profile into the lensing quantities

        :param M: mass enclosed 200 rho_crit in units of M_sun
        :param c: NFW concentration parameter (r200/r_s)
        :return: alpha_Rs (observed bending angle at the scale radius, Rs_angle (angle at scale radius) (in units of arcsec)
        """
        rho0, Rs, r200 = self.nfwParam_physical(M, c)
        Rs_angle = Rs / self.D_d / const.arcsec  # Rs in arcsec
        alpha_Rs = rho0 * (4 * Rs**2 * (1 + np.log(1. / 2.)))
        return Rs_angle, alpha_Rs / self.epsilon_crit / self.D_d / const.arcsec

    def nfwParam_physical(self, M, c):
        """
        returns the NFW parameters in physical units
        :param M: physical mass in M_sun
        :param c: concentration
        :return:
        """
        r200 = self.nfw_param.r200_M(M * self.h) / self.h * self.a_z(
            self.z_lens)  # physical radius r200
        rho0 = self.nfw_param.rho0_c(c) * self.h**2 / self.a_z(
            self.z_lens)**3  # physical density in M_sun/Mpc**3
        Rs = r200 / c
        return rho0, Rs, r200

    def sis_theta_E2sigma_v(self, theta_E):
        """
        converts the lensing Einstein radius into a physical velocity dispersion
        :param theta_E: Einstein radius (in arcsec)
        :return: velocity dispersion in units (km/s)
        """
        v_sigma_c2 = theta_E * const.arcsec / (4 *
                                               np.pi) * self.D_s / self.D_ds
        return np.sqrt(v_sigma_c2) * const.c / 1000

    def sis_sigma_v2theta_E(self, v_sigma):
        """
        converts the velocity dispersion into an Einstein radius for a SIS profile
        :param v_sigma: velocity dispersion (km/s)
        :return: theta_E (arcsec)
        """
        theta_E = 4 * np.pi * (
            v_sigma * 1000. / const.c)**2 * self.D_ds / self.D_s / const.arcsec
        return theta_E
Example #19
0
class LensCosmo(object):
    """
    class to manage the physical units and distances present in a single plane lens with fixed input cosmology
    """
    def __init__(self, z_lens, z_source, cosmo=None):
        """

        :param z_lens: redshift of lens
        :param z_source: redshift of source
        :param cosmo: astropy.cosmology instance
        """

        self.z_lens = z_lens
        self.z_source = z_source
        self.background = Background(cosmo=cosmo)
        self.nfw_param = NFWParam(cosmo=cosmo)

    def a_z(self, z):
        """
        convert redshift into scale factor
        :param z: redshift
        :return: scale factor
        """
        return 1. / (1. + z)

    @property
    def h(self):
        return self.background.cosmo.H(0).value / 100.

    @property
    def dd(self):
        """

        :return: angular diameter distance to the deflector [Mpc]
        """
        return self.background.d_xy(0, self.z_lens)

    @property
    def ds(self):
        """

        :return: angular diameter distance to the source [Mpc]
        """
        return self.background.d_xy(0, self.z_source)

    @property
    def dds(self):
        """

        :return: angular diameter distance from deflector to source [Mpc]
        """
        return self.background.d_xy(self.z_lens, self.z_source)

    @property
    def ddt(self):
        """

        :return: time delay distance [Mpc]
        """
        return (1 + self.z_lens) * self.dd * self.ds / self.dds

    @property
    def sigma_crit(self):
        """
        returns the critical projected lensing mass density in units of M_sun/Mpc^2
        :return: critical projected lensing mass density
        """
        if not hasattr(self, '_sigma_crit_mpc'):
            const_SI = const.c**2 / (4 * np.pi * const.G
                                     )  # c^2/(4*pi*G) in units of [kg/m]
            conversion = const.Mpc / const.M_sun  # converts [kg/m] to [M_sun/Mpc]
            factor = const_SI * conversion  # c^2/(4*pi*G) in units of [M_sun/Mpc]
            self._sigma_crit_mpc = self.ds / (
                self.dd * self.dds) * factor  # [M_sun/Mpc^2]
        return self._sigma_crit_mpc

    @property
    def sigma_crit_angle(self):
        """
        returns the critical surface density in units of M_sun/arcsec^2 (in physical solar mass units)
        when provided a physical mass per physical Mpc^2
        :return: critical projected mass density
        """
        if not hasattr(self, '_sigma_crit_arcsec'):
            const_SI = const.c**2 / (4 * np.pi * const.G
                                     )  # c^2/(4*pi*G) in units of [kg/m]
            conversion = const.Mpc / const.M_sun  # converts [kg/m] to [M_sun/Mpc]
            factor = const_SI * conversion  # c^2/(4*pi*G) in units of [M_sun/Mpc]
            self._sigma_crit_arcsec = self.ds / (
                self.dd * self.dds) * factor * (
                    self.dd * const.arcsec)**2  # [M_sun/arcsec^2]
        return self._sigma_crit_arcsec

    def phys2arcsec_lens(self, phys):
        """
        convert physical Mpc into arc seconds
        :param phys: physical distance [Mpc]
        :return: angular diameter [arcsec]
        """
        return phys / self.dd / const.arcsec

    def arcsec2phys_lens(self, arcsec):
        """
        convert angular to physical quantities for lens plane
        :param arcsec: angular size at lens plane [arcsec]
        :return: physical size at lens plane [Mpc]
        """
        return arcsec * const.arcsec * self.dd

    def arcsec2phys_source(self, arcsec):
        """
        convert angular to physical quantities for source plane
        :param arcsec: angular size at source plane [arcsec]
        :return: physical size at source plane [Mpc]
        """
        return arcsec * const.arcsec * self.ds

    def kappa2proj_mass(self, kappa):
        """
        convert convergence to projected mass M_sun/Mpc^2
        :param kappa: lensing convergence
        :return: projected mass [M_sun/Mpc^2]
        """
        return kappa * self.sigma_crit

    def mass_in_theta_E(self, theta_E):
        """
        mass within Einstein radius (area * epsilon crit) [M_sun]
        :param theta_E: Einstein radius [arcsec]
        :return: mass within Einstein radius [M_sun]
        """
        mass = self.arcsec2phys_lens(theta_E)**2 * np.pi * self.sigma_crit
        return mass

    def mass_in_coin(self, theta_E):
        """

        :param theta_E: Einstein radius [arcsec]
        :return: mass in coin calculated in mean density of the universe
        """
        chi_L = self.background.T_xy(0, self.z_lens)
        chi_S = self.background.T_xy(0, self.z_source)
        return 1. / 3 * np.pi * (
            chi_L * theta_E * const.arcsec
        )**2 * chi_S * self.background.rho_crit  #[M_sun/Mpc**3]

    def time_delay_units(self, fermat_pot, kappa_ext=0):
        """

        :param fermat_pot: in units of arcsec^2 (e.g. Fermat potential)
        :param kappa_ext: unit-less external shear not accounted for in the Fermat potential
        :return: time delay in days
        """
        D_dt = self.ddt * (1. - kappa_ext) * const.Mpc  # eqn 7 in Suyu et al.
        return D_dt / const.c * fermat_pot / const.day_s * const.arcsec**2  # * self.arcsec2phys_lens(1.)**2

    def time_delay2fermat_pot(self, dt):
        """

        :param dt: time delay in units of days
        :return: Fermat potential in units arcsec**2 for a given cosmology
        """
        D_dt = self.ddt * const.Mpc
        return dt * const.c * const.day_s / D_dt / const.arcsec**2

    def nfw_angle2physical(self, Rs_angle, alpha_Rs):
        """
        converts the angular parameters into the physical ones for an NFW profile

        :param alpha_Rs: observed bending angle at the scale radius in units of arcsec
        :param Rs: scale radius in units of arcsec
        :return: rho0, Rs, c, r200, M200
        """
        Rs = Rs_angle * const.arcsec * self.dd
        theta_scaled = alpha_Rs * self.sigma_crit * self.dd * const.arcsec
        rho0 = theta_scaled / (4 * Rs**2 * (1 + np.log(1. / 2.)))
        rho0_com = rho0 / self.h**2
        c = self.nfw_param.c_rho0(rho0_com, self.z_lens)
        r200 = c * Rs
        M200 = self.nfw_param.M_r200(r200 * self.h, self.z_lens) / self.h
        return rho0, Rs, c, r200, M200

    def nfw_physical2angle(self, M, c):
        """
        converts the physical mass and concentration parameter of an NFW profile into the lensing quantities

        :param M: mass enclosed 200 rho_crit in units of M_sun (physical units, meaning no little h)
        :param c: NFW concentration parameter (r200/r_s)
        :return: Rs_angle (angle at scale radius) (in units of arcsec), alpha_Rs (observed bending angle at the scale radius
        """
        rho0, Rs, r200 = self.nfwParam_physical(M, c)
        Rs_angle = Rs / self.dd / const.arcsec  # Rs in arcsec
        alpha_Rs = rho0 * (4 * Rs**2 * (1 + np.log(1. / 2.)))
        return Rs_angle, alpha_Rs / self.sigma_crit / self.dd / const.arcsec

    def nfwParam_physical(self, M, c):
        """
        returns the NFW parameters in physical units

        :param M: physical mass in M_sun
        :param c: concentration
        :return: rho0, Rs, r200
        """
        r200 = self.nfw_param.r200_M(
            M * self.h, self.z_lens) / self.h  # physical radius r200
        rho0 = self.nfw_param.rho0_c(
            c, self.z_lens) * self.h**2  # physical density in M_sun/Mpc**3
        Rs = r200 / c
        return rho0, Rs, r200

    def nfw_M_theta_vir(self, M):
        """
        returns virial radius in angular units of arc seconds on the sky

        :param M: physical mass in M_sun
        :return: angle (in arc seconds) of the virial radius
        """
        r200 = self.nfw_param.r200_M(
            M * self.h, self.z_lens) / self.h  # physical radius r200
        theta_r200 = r200 / self.dd / const.arcsec
        return theta_r200

    def sis_theta_E2sigma_v(self, theta_E):
        """
        converts the lensing Einstein radius into a physical velocity dispersion
        :param theta_E: Einstein radius (in arcsec)
        :return: velocity dispersion in units (km/s)
        """
        v_sigma_c2 = theta_E * const.arcsec / (4 * np.pi) * self.ds / self.dds
        return np.sqrt(v_sigma_c2) * const.c / 1000

    def sis_sigma_v2theta_E(self, v_sigma):
        """
        converts the velocity dispersion into an Einstein radius for a SIS profile
        :param v_sigma: velocity dispersion (km/s)
        :return: theta_E (arcsec)
        """
        theta_E = 4 * np.pi * (v_sigma * 1000. /
                               const.c)**2 * self.dds / self.ds / const.arcsec
        return theta_E

    def uldm_angular2phys(self, kappa_0, theta_c):
        """
        converts the anguar parameters entering the LensModel Uldm() (Ultra Light
        Dark Matter) class in physical masses, i.e. the total soliton mass and the
        mass of the particle
        :param kappa_0: central convergence of profile
        :param theta_c: core radius (in arcseconds)
        :return: m_eV_log10, M_sol_log10, the log10 of the masses, m in eV and M in M_sun
        """
        D_Lens = self.dd * 10**6  # in parsec
        Sigma_c = self.sigma_crit * 10**(-12)  # in M_sun / parsec^2
        r_c = theta_c * const.arcsec * D_Lens
        rho0 = 2048 * np.sqrt(0.091) * kappa_0 * Sigma_c / (429 * np.pi * r_c)
        m_log10 = -22 + 0.5 * np.log10(190 / rho0 * (r_c / 100)**(-4))
        M_log10 = 9 + np.log10(160 * 1.4 / r_c) - 2 * (m_log10 + 22)
        return m_log10, M_log10

    def uldm_mphys2angular(self, m_log10, M_log10):
        """
        converts physical ULDM mass in the ones, in angular units, that enter
        the LensModel Uldm() class
        :param m_log10: exponent of ULDM mass in eV
        :param M_log10: exponent of soliton mass in M_sun
        :return: kappa_0, theta_c, the central convergence and core radius (in arcseconds)
        """
        D_Lens = self.dd * 10**6  # in parsec
        Sigma_c = self.sigma_crit * 10**(-12)  # in M_sun/parsec^2
        m22 = 10**(m_log10 + 22)
        M9 = 10**(M_log10 - 9)
        r_c = 160 * 1.4 * m22**(-2) * M9**(-1)  # core radius in parsec
        rho0 = 190 * m22**(-2) * (r_c / 100)**(
            -4)  # central density in M_sun/parsec^3
        kappa_0 = 429 * np.pi * rho0 * r_c / (2048 * np.sqrt(0.091) * Sigma_c)
        theta_c = r_c / D_Lens / const.arcsec
        return kappa_0, theta_c