def radial_light_profile(self, r_list, kwargs_light, center_x=None, center_y=None, model_bool_list=None): """ :param r_list: list of radii to compute the spherically averaged lens light profile :param center_x: center of the profile :param center_y: center of the profile :param kwargs_light: lens light parameter keyword argument list :param model_bool_list: bool list or None, indicating which profiles to sum over :return: flux amplitudes at r_list radii spherically averaged """ center_x, center_y = analysis_util.profile_center( kwargs_light, center_x, center_y) f_list = [] for r in r_list: x, y = util.points_on_circle(r, num_points=20) f_r = self._light_model.surface_brightness( x + center_x, y + center_y, kwargs_list=kwargs_light, k=model_bool_list) f_list.append(np.average(f_r)) return f_list
def ellipticity(self, kwargs_light, grid_spacing, grid_num, center_x=None, center_y=None, model_bool_list=None): """ make sure that the window covers all the light, otherwise the moments may give a too low answers. :param kwargs_light: keyword argument list of profiles :param center_x: center of profile, if None takes it from the first profile in kwargs_light :param center_y: center of profile, if None takes it from the first profile in kwargs_light :param model_bool_list: list of booleans to select subsets of the profile :param grid_spacing: grid spacing over which the moments are computed :param grid_num: grid size over which the moments are computed :return: eccentricities e1, e2 """ center_x, center_y = analysis_util.profile_center( kwargs_light, center_x, center_y) if model_bool_list is None: model_bool_list = [True] * len(kwargs_light) x_grid, y_grid = util.make_grid(numPix=grid_num, deltapix=grid_spacing) x_grid += center_x y_grid += center_y I_xy = self._light_model.surface_brightness(x_grid, y_grid, kwargs_light, k=model_bool_list) e1, e2 = analysis_util.ellipticities(I_xy, x_grid - center_x, y_grid - center_y) return e1, e2
def half_light_radius(self, kwargs_light, grid_spacing, grid_num, center_x=None, center_y=None, model_bool_list=None): """ computes numerically the half-light-radius of the deflector light and the total photon flux :param kwargs_light: keyword argument list of profiles :param center_x: center of profile, if None takes it from the first profile in kwargs_light :param center_y: center of profile, if None takes it from the first profile in kwargs_light :param model_bool_list: list of booleans to select subsets of the profile :param grid_spacing: grid spacing over which the moments are computed :param grid_num: grid size over which the moments are computed :return: half-light radius """ center_x, center_y = analysis_util.profile_center( kwargs_light, center_x, center_y) if model_bool_list is None: model_bool_list = [True] * len(kwargs_light) x_grid, y_grid = util.make_grid(numPix=grid_num, deltapix=grid_spacing) x_grid += center_x y_grid += center_y lens_light = self._light_model.surface_brightness(x_grid, y_grid, kwargs_light, k=model_bool_list) R_h = analysis_util.half_light_radius(lens_light, x_grid, y_grid, center_x, center_y) return R_h
def mst_invariant_differential(self, kwargs_lens, radius, center_x=None, center_y=None, model_list_bool=None, num_points=10): """ Average of the radial stretch differential in radial direction, divided by the radial stretch factor. .. math:: \\xi = \\frac{\\partial \\lambda_{\\rm rad}}{\\partial r} \\frac{1}{\\lambda_{\\rm rad}} This quantity is invariant under the MST. The specific definition is provided by Birrer 2021. Equivalent (proportional) definitions are provided by e.g. Kochanek 2020, Sonnenfeld 2018. :param kwargs_lens: lens model keyword argument list :param radius: radius from the center where to compute the MST invariant differential :param center_x: center position :param center_y: center position :param model_list_bool: indicate which part of the model to consider :param num_points: number of estimates around the radius :return: xi """ center_x, center_y = analysis_util.profile_center( kwargs_lens, center_x, center_y) x, y = util.points_on_circle(radius, num_points) ext = LensModelExtensions(lensModel=self._lens_model) lambda_rad, lambda_tan, orientation_angle, dlambda_tan_dtan, dlambda_tan_drad, dlambda_rad_drad, dlambda_rad_dtan, dphi_tan_dtan, dphi_tan_drad, dphi_rad_drad, dphi_rad_dtan = ext.radial_tangential_differentials( x, y, kwargs_lens, center_x=center_x, center_y=center_y) xi = np.mean(dlambda_rad_drad / lambda_rad) return xi
def effective_einstein_radius(self, kwargs_lens, center_x=None, center_y=None, model_bool_list=None, grid_num=200, grid_spacing=0.05, get_precision=False, verbose=True): """ computes the radius with mean convergence=1 :param kwargs_lens: list of lens model keyword arguments :param spacing: number of annular bins to compute the convergence (resolution of the Einstein radius estimate) :param get_precision: If `True`, return the precision of estimated Einstein radius :return: estimate of the Einstein radius """ center_x, center_y = analysis_util.profile_center(kwargs_lens, center_x, center_y) x_grid, y_grid = util.make_grid(numPix=grid_num, deltapix=grid_spacing) x_grid += center_x y_grid += center_y kappa = self._lens_model.kappa(x_grid, y_grid, kwargs_lens, k=model_bool_list) if self._lens_model.lens_model_list[0] in ['INTERPOL', 'INTERPOL_SCALED']: center_x = x_grid[kappa == np.max(kappa)][0] center_y = y_grid[kappa == np.max(kappa)][0] #kappa = util.array2image(kappa) r_array = np.linspace(0, grid_num*grid_spacing/2., grid_num*2) for r in r_array: mask = np.array(1 - mask_util.mask_center_2d(center_x, center_y, r, x_grid, y_grid)) sum_mask = np.sum(mask) if sum_mask > 0: kappa_mean = np.sum(kappa*mask)/np.sum(mask) if kappa_mean < 1: if get_precision: return r, r_array[1] - r_array[0] else: return r if verbose: Warning("No Einstein radius computed for the following model!", kwargs_lens) return np.nan
def profile_slope(self, kwargs_lens, radius, center_x=None, center_y=None, model_list_bool=None, num_points=10): """ computes the logarithmic power-law slope of a profile. ATTENTION: this is not an observable! :param kwargs_lens: lens model keyword argument list :param radius: radius from the center where to compute the logarithmic slope (angular units :param model_list_bool: bool list, indicate which part of the model to consider :param num_points: number of estimates around the Einstein radius :return: logarithmic power-law slope """ center_x, center_y = analysis_util.profile_center( kwargs_lens, center_x, center_y) x, y = util.points_on_circle(radius, num_points) dr = 0.01 x_dr, y_dr = util.points_on_circle(radius + dr, num_points) alpha_E_x_i, alpha_E_y_i = self._lens_model.alpha(center_x + x, center_y + y, kwargs_lens, k=model_list_bool) alpha_E_r = np.sqrt(alpha_E_x_i**2 + alpha_E_y_i**2) alpha_E_dr_x_i, alpha_E_dr_y_i = self._lens_model.alpha( center_x + x_dr, center_y + y_dr, kwargs_lens, k=model_list_bool) alpha_E_dr = np.sqrt(alpha_E_dr_x_i**2 + alpha_E_dr_y_i**2) slope = np.mean( np.log(alpha_E_dr / alpha_E_r) / np.log((radius + dr) / radius)) gamma = -slope + 2 return gamma
def effective_einstein_radius(self, kwargs_lens, center_x=None, center_y=None, model_bool_list=None, grid_num=200, grid_spacing=0.05, get_precision=False, verbose=True): """ computes the radius with mean convergence=1 :param kwargs_lens: list of lens model keyword arguments :param center_x: position of the center (if not set, is attempting to find it from the parameters kwargs_lens) :param center_y: position of the center (if not set, is attempting to find it from the parameters kwargs_lens) :param model_bool_list: list of booleans indicating the addition (=True) of a model component in computing the Einstein radius :param grid_num: integer, number of grid points to numerically evaluate the convergence and estimate the Einstein radius :param grid_spacing: spacing in angular units of the grid :param get_precision: If `True`, return the precision of estimated Einstein radius :param verbose: boolean, if True prints warning if indication of insufficient result :return: estimate of the Einstein radius """ center_x, center_y = analysis_util.profile_center( kwargs_lens, center_x, center_y) x_grid, y_grid = util.make_grid(numPix=grid_num, deltapix=grid_spacing) x_grid += center_x y_grid += center_y kappa = self._lens_model.kappa(x_grid, y_grid, kwargs_lens, k=model_bool_list) if self._lens_model.lens_model_list[0] in [ 'INTERPOL', 'INTERPOL_SCALED' ]: center_x = x_grid[kappa == np.max(kappa)][0] center_y = y_grid[kappa == np.max(kappa)][0] #kappa = util.array2image(kappa) r_array = np.linspace(0, grid_num * grid_spacing / 2., grid_num * 2) for r in r_array: mask = np.array(1 - mask_util.mask_center_2d( center_x, center_y, r, x_grid, y_grid)) sum_mask = np.sum(mask) if sum_mask > 0: kappa_mean = np.sum(kappa * mask) / np.sum(mask) if kappa_mean < 1: if get_precision: return r, r_array[1] - r_array[0] else: return r if verbose: Warning("No Einstein radius computed for the following model!", kwargs_lens) return np.nan
def multi_gaussian_decomposition_ellipse(self, kwargs_light, model_bool_list=None, center_x=None, center_y=None, grid_num=100, grid_spacing=0.05, n_comp=20): """ MGE with ellipticity estimate. Attention: numerical grid settings for ellipticity estimate and radial MGE may not necessarily be the same! :param kwargs_light: keyword argument list of profiles :param center_x: center of profile, if None takes it from the first profile in kwargs_light :param center_y: center of profile, if None takes it from the first profile in kwargs_light :param model_bool_list: list of booleans to select subsets of the profile :param grid_spacing: grid spacing over which the moments are computed :param grid_num: grid size over which the moments are computed :param n_comp: maximum number of Gaussians in the MGE :return: keyword arguments of the elliptical multi Gaussian profile in lenstronomy conventions """ # estimate center center_x, center_y = analysis_util.profile_center( kwargs_light, center_x, center_y) e1, e2 = self.ellipticity(kwargs_light, center_x=center_x, center_y=center_y, model_bool_list=model_bool_list, grid_spacing=grid_spacing * 2, grid_num=grid_num) # MGE around major axis amplitudes, sigmas, center_x, center_y = self.multi_gaussian_decomposition( kwargs_light, model_bool_list=model_bool_list, n_comp=n_comp, grid_spacing=grid_spacing, grid_num=grid_num, center_x=center_x, center_y=center_y) kwargs_mge = { 'amp': amplitudes, 'sigma': sigmas, 'center_x': center_x, 'center_y': center_y } kwargs_mge['e1'] = e1 kwargs_mge['e2'] = e2 return kwargs_mge
def multi_gaussian_lens(self, kwargs_lens, center_x=None, center_y=None, model_bool_list=None, n_comp=20): """ multi-gaussian lens model in convergence space :param kwargs_lens: :param n_comp: :return: """ center_x, center_y = analysis_util.profile_center(kwargs_lens, center_x, center_y) theta_E = self.effective_einstein_radius(kwargs_lens) r_array = np.logspace(-4, 2, 200) * theta_E kappa_s = self.radial_lens_profile(r_array, kwargs_lens, center_x=center_x, center_y=center_y, model_bool_list=model_bool_list) amplitudes, sigmas, norm = mge.mge_1d(r_array, kappa_s, N=n_comp) return amplitudes, sigmas, center_x, center_y
def multi_gaussian_decomposition(self, kwargs_light, model_bool_list=None, n_comp=20, center_x=None, center_y=None, r_h=None, grid_spacing=0.02, grid_num=200): """ multi-gaussian decomposition of the lens light profile (in 1-dimension) :param kwargs_light: keyword argument list of profiles :param center_x: center of profile, if None takes it from the first profile in kwargs_light :param center_y: center of profile, if None takes it from the first profile in kwargs_light :param model_bool_list: list of booleans to select subsets of the profile :param grid_spacing: grid spacing over which the moments are computed for the half-light radius :param grid_num: grid size over which the moments are computed :param n_comp: maximum number of Gaussian's in the MGE :param r_h: float, half light radius to be used for MGE (optional, otherwise using a numerical grid) :return: amplitudes, sigmas, center_x, center_y """ center_x, center_y = analysis_util.profile_center( kwargs_light, center_x, center_y) if r_h is None: r_h = self.half_light_radius(kwargs_light, center_x=center_x, center_y=center_y, model_bool_list=model_bool_list, grid_spacing=grid_spacing, grid_num=grid_num) r_array = np.logspace(-3, 2, 200) * r_h * 2 flux_r = self.radial_light_profile(r_array, kwargs_light, center_x=center_x, center_y=center_y, model_bool_list=model_bool_list) amplitudes, sigmas, norm = mge.mge_1d(r_array, flux_r, N=n_comp) return amplitudes, sigmas, center_x, center_y
def test_raise(self): with self.assertRaises(ValueError): kwargs_list = [{}] analysis_util.profile_center(kwargs_list=kwargs_list)
def test_profile_center(self): kwargs_list = [{'center_x': 1, 'center_y': 0}] center_x, center_y = analysis_util.profile_center( kwargs_list=kwargs_list) assert center_x == 1 assert center_y == 0