Source code for lenstronomy.LensModel.multi_plane_base

import numpy as np
from lenstronomy.Cosmo.background import Background
from lenstronomy.LensModel.profile_list_base import ProfileListBase
import lenstronomy.Util.constants as const

__all__ = ['MultiPlaneBase']


[docs]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 source redshift of the class instance. """ 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): """ :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) """ 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) 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
[docs] 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
[docs] 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 :param include_z_start: boolean, if True includes the computation of the starting position if the first deflector is at z_start :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
[docs] 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 ascending 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 index_lens: integer, index of the deflector plane :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): """ :param inclusive: boolean, if True selects z_lens including z_start, else only selects z_lens > z_start :param z_lens: deflector redshift :param z_start: starting redshift (lowest redshift) :return: boolean of condition """ if inclusive: return z_lens >= z_start else: return z_lens > z_start