class TestConvergence(object): """ tests the Gaussian methods """ def setup(self): self.profile = Convergence() self.kwargs_lens = {'kappa_ext': 0.1} def test_function(self): x = np.array([1]) y = np.array([0]) values = self.profile.function(x, y, **self.kwargs_lens) npt.assert_almost_equal(values[0], self.kwargs_lens['kappa_ext'] / 2, decimal=5) x = np.array([0]) y = np.array([0]) values = self.profile.function(x, y, **self.kwargs_lens) npt.assert_almost_equal(values[0], 0, decimal=5) x = np.array([2, 3, 4]) y = np.array([1, 1, 1]) values = self.profile.function(x, y, **self.kwargs_lens) npt.assert_almost_equal(values[0], 0.25, decimal=5) npt.assert_almost_equal(values[1], 0.5, decimal=5) def test_derivatives(self): x = np.array([1]) y = np.array([2]) f_x, f_y = self.profile.derivatives(x, y, **self.kwargs_lens) npt.assert_almost_equal(f_x[0], 0.1, decimal=5) npt.assert_almost_equal(f_y[0], 0.2, decimal=5) x = np.array([1, 3, 4]) y = np.array([2, 1, 1]) values = self.profile.derivatives(x, y, **self.kwargs_lens) npt.assert_almost_equal(values[0][0], 0.1, decimal=5) npt.assert_almost_equal(values[1][0], 0.2, decimal=5) def test_hessian(self): x = np.array([1]) y = np.array([2]) f_xx, f_xy, f_yx, f_yy = self.profile.hessian(x, y, **self.kwargs_lens) npt.assert_almost_equal(f_xx, 0.1, decimal=5) npt.assert_almost_equal(f_yy, 0.1, decimal=5) npt.assert_almost_equal(f_xy, 0, decimal=5) npt.assert_almost_equal(f_yx, 0, decimal=5) x = np.array([1, 3, 4]) y = np.array([2, 1, 1]) values = self.profile.hessian(x, y, **self.kwargs_lens) npt.assert_almost_equal(values[0], 0.1, decimal=5) npt.assert_almost_equal(values[3], 0.1, decimal=5) npt.assert_almost_equal(values[1], 0, decimal=5)
class CurvedArcConstMST(LensProfileBase): """ lens model that describes a section of a highly magnified deflector region. The parameterization is chosen to describe local observables efficient. Observables are: - curvature radius (basically bending relative to the center of the profile) - radial stretch (plus sign) thickness of arc with parity (more generalized than the power-law slope) - tangential stretch (plus sign). Infinity means at critical curve - direction of curvature - position of arc Requirements: - Should work with other perturbative models without breaking its meaning (say when adding additional shear terms) - Must best reflect the observables in lensing - minimal covariances between the parameters, intuitive parameterization. """ param_names = [ 'tangential_stretch', 'radial_stretch', 'curvature', 'direction', 'center_x', 'center_y' ] lower_limit_default = { 'tangential_stretch': -100, 'radial_stretch': -5, 'curvature': 0.000001, 'direction': -np.pi, 'center_x': -100, 'center_y': -100 } upper_limit_default = { 'tangential_stretch': 100, 'radial_stretch': 5, 'curvature': 100, 'direction': np.pi, 'center_x': 100, 'center_y': 100 } def __init__(self): self._mst = Convergence() self._curve = CurvedArcConst() super(CurvedArcConstMST, self).__init__() def function(self, x, y, tangential_stretch, radial_stretch, curvature, direction, center_x, center_y): """ ATTENTION: there may not be a global lensing potential! :param x: :param y: :param tangential_stretch: float, stretch of intrinsic source in tangential direction :param radial_stretch: float, stretch of intrinsic source in radial direction :param curvature: 1/curvature radius :param direction: float, angle in radian :param center_x: center of source in image plane :param center_y: center of source in image plane :return: """ raise NotImplemented( 'lensing potential for regularly curved arc is not implemented') def derivatives(self, x, y, tangential_stretch, radial_stretch, curvature, direction, center_x, center_y): """ :param x: :param y: :param tangential_stretch: float, stretch of intrinsic source in tangential direction :param radial_stretch: float, stretch of intrinsic source in radial direction :param curvature: 1/curvature radius :param direction: float, angle in radian :param center_x: center of source in image plane :param center_y: center of source in image plane :return: """ lambda_mst = 1. / radial_stretch kappa_ext = 1 - lambda_mst curve_stretch = tangential_stretch / radial_stretch f_x_curve, f_y_curve = self._curve.derivatives(x, y, curve_stretch, curvature, direction, center_x, center_y) f_x_mst, f_y_mst = self._mst.derivatives(x, y, kappa_ext, ra_0=center_x, dec_0=center_y) f_x = lambda_mst * f_x_curve + f_x_mst f_y = lambda_mst * f_y_curve + f_y_mst return f_x, f_y def hessian(self, x, y, tangential_stretch, radial_stretch, curvature, direction, center_x, center_y): """ :param x: :param y: :param tangential_stretch: float, stretch of intrinsic source in tangential direction :param radial_stretch: float, stretch of intrinsic source in radial direction :param curvature: 1/curvature radius :param direction: float, angle in radian :param center_x: center of source in image plane :param center_y: center of source in image plane :return: """ lambda_mst = 1. / radial_stretch kappa_ext = 1 - lambda_mst curve_stretch = tangential_stretch / radial_stretch f_xx_c, f_xy_c, f_yx_c, f_yy_c = self._curve.hessian( x, y, curve_stretch, curvature, direction, center_x, center_y) f_xx_mst, f_xy_mst, f_yx_mst, f_yy_mst = self._mst.hessian( x, y, kappa_ext, ra_0=center_x, dec_0=center_y) f_xx = lambda_mst * f_xx_c + f_xx_mst f_xy = lambda_mst * f_xy_c + f_xy_mst f_yx = lambda_mst * f_yx_c + f_yx_mst f_yy = lambda_mst * f_yy_c + f_yy_mst return f_xx, f_xy, f_yx, f_yy
class TestCurvedArcSISMST(object): """ tests the source model routines """ def setup(self): self.model = CurvedArcSISMST() self.sis = SIS() self.mst = Convergence() def test_spp2stretch(self): center_x, center_y = 1, 1 theta_E = 1 kappa = 0.1 center_x_spp, center_y_spp = 0., 0 tangential_stretch, radial_stretch, curvature, direction = self.model.sis_mst2stretch(theta_E, kappa, center_x_spp, center_y_spp, center_x, center_y) theta_E_new, kappa_new, center_x_spp_new, center_y_spp_new = self.model.stretch2sis_mst(tangential_stretch, radial_stretch, curvature, direction, center_x, center_y) npt.assert_almost_equal(center_x_spp_new, center_x_spp, decimal=8) npt.assert_almost_equal(center_y_spp_new, center_y_spp, decimal=8) npt.assert_almost_equal(theta_E_new, theta_E, decimal=8) npt.assert_almost_equal(kappa_new, kappa, decimal=8) center_x, center_y = -1, 1 tangential_stretch, radial_stretch, curvature, direction = self.model.sis_mst2stretch(theta_E, kappa, center_x_spp, center_y_spp, center_x, center_y) theta_E_new, kappa_new, center_x_spp_new, center_y_spp_new = self.model.stretch2sis_mst(tangential_stretch, radial_stretch, curvature, direction, center_x, center_y) npt.assert_almost_equal(center_x_spp_new, center_x_spp, decimal=8) npt.assert_almost_equal(center_y_spp_new, center_y_spp, decimal=8) npt.assert_almost_equal(theta_E_new, theta_E, decimal=8) npt.assert_almost_equal(kappa_new, kappa, decimal=8) center_x, center_y = 0, 0.5 tangential_stretch, radial_stretch, curvature, direction = self.model.sis_mst2stretch(theta_E, kappa, center_x_spp, center_y_spp, center_x, center_y) theta_E_new, kappa_new, center_x_spp_new, center_y_spp_new = self.model.stretch2sis_mst(tangential_stretch, radial_stretch, curvature, direction, center_x, center_y) npt.assert_almost_equal(center_x_spp_new, center_x_spp, decimal=8) npt.assert_almost_equal(center_y_spp_new, center_y_spp, decimal=8) npt.assert_almost_equal(theta_E_new, theta_E, decimal=8) npt.assert_almost_equal(kappa_new, kappa, decimal=8) center_x, center_y = 0, -1.5 tangential_stretch, radial_stretch, r_curvature, direction = self.model.sis_mst2stretch(theta_E, kappa, center_x_spp, center_y_spp, center_x, center_y) print(tangential_stretch, radial_stretch, r_curvature, direction) theta_E_new, kappa_new, center_x_spp_new, center_y_spp_new = self.model.stretch2sis_mst(tangential_stretch, radial_stretch, r_curvature, direction, center_x, center_y) npt.assert_almost_equal(center_x_spp_new, center_x_spp, decimal=8) npt.assert_almost_equal(center_y_spp_new, center_y_spp, decimal=8) npt.assert_almost_equal(theta_E_new, theta_E, decimal=8) npt.assert_almost_equal(kappa_new, kappa, decimal=8) def test_function(self): center_x, center_y = 0., 0. x, y = 1, 1 radial_stretch = 1 output = self.model.function(x, y, tangential_stretch=2, radial_stretch=radial_stretch, curvature=1./2, direction=0, center_x=center_x, center_y=center_y) theta_E, kappa_ext, center_x_sis, center_y_sis = self.model.stretch2sis_mst(tangential_stretch=2, radial_stretch=radial_stretch, curvature=1./2, direction=0, center_x=center_x, center_y=center_y) f_sis_out = self.sis.function(1, 1, theta_E, center_x_sis, center_y_sis) # - self.sis.function(0, 0, theta_E, center_x_sis, center_y_sis) alpha_x, alpha_y = self.sis.derivatives(center_x, center_y, theta_E, center_x_sis, center_y_sis) f_sis_0_out = alpha_x * (x - center_x) + alpha_y * (y - center_y) f_mst_out = self.mst.function(x, y, kappa_ext, ra_0=center_x, dec_0=center_y) lambda_mst = 1. / radial_stretch f_out = lambda_mst * (f_sis_out - f_sis_0_out) + f_mst_out npt.assert_almost_equal(output, f_out, decimal=8) def test_derivatives(self): tangential_stretch = 5 radial_stretch = 1 curvature = 1./10 direction = 0.3 center_x = 0 center_y = 0 x, y = 1, 1 theta_E, kappa, center_x_spp, center_y_spp = self.model.stretch2sis_mst(tangential_stretch, radial_stretch, curvature, direction, center_x, center_y) f_x_sis, f_y_sis = self.sis.derivatives(x, y, theta_E, center_x_spp, center_y_spp) f_x_mst, f_y_mst = self.mst.derivatives(x, y, kappa, ra_0=center_x, dec_0=center_y) f_x0, f_y0 = self.sis.derivatives(center_x, center_y, theta_E, center_x_spp, center_y_spp) f_x_new, f_y_new = self.model.derivatives(x, y, tangential_stretch, radial_stretch, curvature, direction, center_x, center_y) npt.assert_almost_equal(f_x_new, f_x_sis + f_x_mst - f_x0, decimal=8) npt.assert_almost_equal(f_y_new, f_y_sis + f_y_mst - f_y0, decimal=8) def test_hessian(self): lens = LensModel(lens_model_list=['CURVED_ARC_SIS_MST']) center_x, center_y = 0, 0 tangential_stretch = 10 radial_stretch = 1 kwargs_lens = [ {'tangential_stretch': tangential_stretch, 'radial_stretch': radial_stretch, 'curvature': 1./10.5, 'direction': 0., 'center_x': center_x, 'center_y': center_y}] mag = lens.magnification(center_x, center_y, kwargs=kwargs_lens) npt.assert_almost_equal(mag, tangential_stretch * radial_stretch, decimal=8) center_x, center_y = 2, 3 tangential_stretch = 10 radial_stretch = 1 kwargs_lens = [ {'tangential_stretch': tangential_stretch, 'radial_stretch': radial_stretch, 'curvature': 1./10.5, 'direction': 0., 'center_x': center_x, 'center_y': center_y}] mag = lens.magnification(center_x, center_y, kwargs=kwargs_lens) npt.assert_almost_equal(mag, tangential_stretch * radial_stretch, decimal=8) center_x, center_y = 0, 0 tangential_stretch = 5 radial_stretch = 1.2 kwargs_lens = [ {'tangential_stretch': tangential_stretch, 'radial_stretch': radial_stretch, 'curvature': 1. / 10.5, 'direction': 0., 'center_x': center_x, 'center_y': center_y}] mag = lens.magnification(center_x, center_y, kwargs=kwargs_lens) npt.assert_almost_equal(mag, tangential_stretch * radial_stretch, decimal=8) center_x, center_y = 0, 0 tangential_stretch = 3 radial_stretch = -1 kwargs_lens = [ {'tangential_stretch': tangential_stretch, 'radial_stretch': radial_stretch, 'curvature': 1./10.5, 'direction': 0., 'center_x': center_x, 'center_y': center_y}] mag = lens.magnification(center_x, center_y, kwargs=kwargs_lens) print(tangential_stretch, radial_stretch, 'stretches') npt.assert_almost_equal(mag, tangential_stretch * radial_stretch, decimal=8) center_x, center_y = 0, 0 tangential_stretch = -3 radial_stretch = -1 kwargs_lens = [ {'tangential_stretch': tangential_stretch, 'radial_stretch': radial_stretch, 'curvature': 1./10.5, 'direction': 0., 'center_x': center_x, 'center_y': center_y}] mag = lens.magnification(center_x, center_y, kwargs=kwargs_lens) npt.assert_almost_equal(mag, tangential_stretch * radial_stretch, decimal=8) center_x, center_y = 0, 0 tangential_stretch = 10.4 radial_stretch = 0.6 kwargs_lens = [ {'tangential_stretch': tangential_stretch, 'radial_stretch': radial_stretch, 'curvature': 1./10.5, 'direction': 0., 'center_x': center_x, 'center_y': center_y}] mag = lens.magnification(center_x, center_y, kwargs=kwargs_lens) npt.assert_almost_equal(mag, tangential_stretch * radial_stretch, decimal=8) def test_curved_arc_recovery(self): """ test whether the curved arc parameters are satisfied in differential form """ ext = LensModelExtensions(LensModel(lens_model_list=['CURVED_ARC_SIS_MST'])) center_x, center_y = 1, 1. # test works except at (0,0) where the direction angle is not well defined tangential_stretch = 10. radial_stretch = 1.2 curvature, direction = 0.02, 0.5 kwargs_lens = {'tangential_stretch': tangential_stretch, 'radial_stretch': radial_stretch, 'curvature': curvature, 'direction': direction, 'center_x': center_x, 'center_y': center_y} self._test_curved_arc_recovery(kwargs_lens) def _test_curved_arc_recovery(self, kwargs_arc_init): ext = LensModelExtensions(LensModel(lens_model_list=['CURVED_ARC_SIS_MST'])) center_x, center_y = kwargs_arc_init['center_x'], kwargs_arc_init['center_y'] kwargs_arc = ext.curved_arc_estimate(center_x, center_y, [kwargs_arc_init]) 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(center_x, center_y, [kwargs_arc_init]) npt.assert_almost_equal(kwargs_arc['tangential_stretch'], kwargs_arc_init['tangential_stretch'], decimal=3) npt.assert_almost_equal(kwargs_arc['radial_stretch'], kwargs_arc_init['radial_stretch'], decimal=3) npt.assert_almost_equal(kwargs_arc['curvature'], kwargs_arc_init['curvature'], decimal=3) npt.assert_almost_equal(dphi_tan_dtan, kwargs_arc_init['curvature'], decimal=3) npt.assert_almost_equal(kwargs_arc['direction'], kwargs_arc_init['direction'], decimal=3) npt.assert_almost_equal(dlambda_tan_dtan, 0, decimal=3)
class CoredDensityMST(LensProfileBase): """ approximate mass-sheet transform of a density core. This routine takes the parameters of the density core and subtracts a mass=sheet that approximates the cored profile in it's center to counter-act (in approximation) this model. This allows for better sampling of the mass-sheet transformed quantities that do not have strong covariances. Attention!!! The interpretation of the result is that the mass sheet as 'CONVERGENCE' that is present needs to be subtracted in post-processing. """ param_names = ['lambda_approx', 'r_core', 'center_x', 'center_y'] lower_limit_default = { 'lambda_approx': -1, 'r_core': 0, 'center_x': -100, 'center_y': -100 } upper_limit_default = { 'lambda_approx': 10, 'r_core': 100, 'center_x': 100, 'center_y': 100 } def __init__(self, profile_type='CORED_DENSITY'): if profile_type == 'CORED_DENSITY': self._profile = CoredDensity() elif profile_type == 'CORED_DENSITY_2': self._profile = CoredDensity2() elif profile_type == 'CORED_DENSITY_EXP': self._profile = CoredDensityExp() else: raise ValueError( 'profile_type %s not supported for CoredDensityMST instance.' % profile_type) self._convergence = Convergence() super(CoredDensityMST, self).__init__() def function(self, x, y, lambda_approx, r_core, center_x=0, center_y=0): """ lensing potential of approximate mass-sheet correction :param x: x-coordinate :param y: y-coordinate :param lambda_approx: approximate mass sheet transform :param r_core: core radius of the cored density profile :param center_x: x-center of the profile :param center_y: y-center of the profile :return: lensing potential correction """ kappa_ext = 1 - lambda_approx f_cored_density = self._profile.function(x, y, kappa_ext, r_core, center_x, center_y) f_ms = self._convergence.function(x, y, kappa_ext, center_x, center_y) return f_cored_density - f_ms def derivatives(self, x, y, lambda_approx, r_core, center_x=0, center_y=0): """ deflection angles of approximate mass-sheet correction :param x: x-coordinate :param y: y-coordinate :param lambda_approx: approximate mass sheet transform :param r_core: core radius of the cored density profile :param center_x: x-center of the profile :param center_y: y-center of the profile :return: alpha_x, alpha_y """ kappa_ext = 1 - lambda_approx f_x_cd, f_y_cd = self._profile.derivatives(x, y, kappa_ext, r_core, center_x, center_y) f_x_ms, f_y_ms = self._convergence.derivatives(x, y, kappa_ext, center_x, center_y) return f_x_cd - f_x_ms, f_y_cd - f_y_ms def hessian(self, x, y, lambda_approx, r_core, center_x=0, center_y=0): """ Hessian terms of approximate mass-sheet correction :param x: x-coordinate :param y: y-coordinate :param lambda_approx: approximate mass sheet transform :param r_core: core radius of the cored density profile :param center_x: x-center of the profile :param center_y: y-center of the profile :return: df/dxx, df/dyy, df/dxy """ kappa_ext = 1 - lambda_approx f_xx_cd, f_yy_cd, f_xy_cd = self._profile.hessian( x, y, kappa_ext, r_core, center_x, center_y) f_xx_ms, f_yy_ms, f_xy_ms = self._convergence.hessian( x, y, kappa_ext, center_x, center_y) return f_xx_cd - f_xx_ms, f_yy_cd - f_yy_ms, f_xy_cd - f_xy_ms
class CurvedArcTanDiff(LensProfileBase): """ Curved arc model with an additional non-zero tangential stretch differential in tangential direction component Observables are: - curvature radius (basically bending relative to the center of the profile) - radial stretch (plus sign) thickness of arc with parity (more generalized than the power-law slope) - tangential stretch (plus sign). Infinity means at critical curve - direction of curvature - position of arc Requirements: - Should work with other perturbative models without breaking its meaning (say when adding additional shear terms) - Must best reflect the observables in lensing - minimal covariances between the parameters, intuitive parameterization. """ param_names = [ 'tangential_stretch', 'radial_stretch', 'curvature', 'dtan_dtan', 'direction', 'center_x', 'center_y' ] lower_limit_default = { 'tangential_stretch': -100, 'radial_stretch': -5, 'curvature': 0.000001, 'dtan_dtan': -10, 'direction': -np.pi, 'center_x': -100, 'center_y': -100 } upper_limit_default = { 'tangential_stretch': 100, 'radial_stretch': 5, 'curvature': 100, 'dtan_dtan': 10, 'direction': np.pi, 'center_x': 100, 'center_y': 100 } def __init__(self): self._sie = SIE(NIE=True) self._mst = Convergence() super(CurvedArcTanDiff, self).__init__() @staticmethod def stretch2sie_mst(tangential_stretch, radial_stretch, curvature, dtan_dtan, direction, center_x, center_y): """ :param tangential_stretch: float, stretch of intrinsic source in tangential direction :param radial_stretch: float, stretch of intrinsic source in radial direction :param curvature: 1/curvature radius :param dtan_dtan: d(tangential_stretch) / d(tangential direction) / tangential stretch :param direction: float, angle in radian :param center_x: center of source in image plane :param center_y: center of source in image plane :return: parameters in terms of a spherical SIS + MST resulting in the same observables """ center_x_sis, center_y_sis = center_deflector(curvature, direction, center_x, center_y) r_curvature = 1. / curvature lambda_mst = 1. / radial_stretch kappa_ext = 1 - lambda_mst theta_E = r_curvature * (1. - radial_stretch / tangential_stretch) # analytic relation (see Birrer 2021) dlambda_tan_dr = tangential_stretch / r_curvature * ( 1 - tangential_stretch / radial_stretch) # translate tangential eigenvalue gradient in lens ellipticity dtan_dtan_ = dtan_dtan * tangential_stretch epsilon = np.abs(dtan_dtan_ / dlambda_tan_dr) # bound epsilon by (-1, 1) epsilon = np.minimum(epsilon, 0.99999) q = np.sqrt((1 - epsilon) / (1 + epsilon)) if dtan_dtan_ < 0: phi = direction - np.pi / 4 else: phi = direction + np.pi / 4 e1_sie, e2_sie = param_util.phi_q2_ellipticity(phi, q) # ellipticity adopted Einstein radius to match local tangential and radial stretch factor = np.sqrt(1 + q**2) / np.sqrt(2 * q) theta_E_sie = theta_E * factor return theta_E_sie, e1_sie, e2_sie, kappa_ext, center_x_sis, center_y_sis def function(self, x, y, tangential_stretch, radial_stretch, curvature, dtan_dtan, direction, center_x, center_y): """ ATTENTION: there may not be a global lensing potential! :param x: :param y: :param tangential_stretch: float, stretch of intrinsic source in tangential direction :param radial_stretch: float, stretch of intrinsic source in radial direction :param curvature: 1/curvature radius :param direction: float, angle in radian :param dtan_dtan: d(tangential_stretch) / d(tangential direction) / tangential stretch :param center_x: center of source in image plane :param center_y: center of source in image plane :return: """ lambda_mst = 1. / radial_stretch theta_E_sie, e1_sie, e2_sie, kappa_ext, center_x_sis, center_y_sis = self.stretch2sie_mst( tangential_stretch, radial_stretch, curvature, dtan_dtan, direction, center_x, center_y) f_sis = self._sie.function( x, y, theta_E_sie, e1_sie, e2_sie, center_x_sis, center_y_sis ) # - self._sis.function(center_x, center_y, theta_E, center_x_sis, center_y_sis) alpha_x, alpha_y = self._sie.derivatives(center_x, center_y, theta_E_sie, e1_sie, e2_sie, center_x_sis, center_y_sis) f_sis_0 = alpha_x * (x - center_x) + alpha_y * (y - center_y) f_mst = self._mst.function(x, y, kappa_ext, ra_0=center_x, dec_0=center_y) return lambda_mst * (f_sis - f_sis_0) + f_mst def derivatives(self, x, y, tangential_stretch, radial_stretch, curvature, dtan_dtan, direction, center_x, center_y): """ :param x: :param y: :param tangential_stretch: float, stretch of intrinsic source in tangential direction :param radial_stretch: float, stretch of intrinsic source in radial direction :param curvature: 1/curvature radius :param direction: float, angle in radian :param dtan_dtan: d(tangential_stretch) / d(tangential direction) / tangential stretch :param center_x: center of source in image plane :param center_y: center of source in image plane :return: """ lambda_mst = 1. / radial_stretch theta_E_sie, e1_sie, e2_sie, kappa_ext, center_x_sis, center_y_sis = self.stretch2sie_mst( tangential_stretch, radial_stretch, curvature, dtan_dtan, direction, center_x, center_y) f_x_sis, f_y_sis = self._sie.derivatives(x, y, theta_E_sie, e1_sie, e2_sie, center_x_sis, center_y_sis) f_x0, f_y0 = self._sie.derivatives(center_x, center_y, theta_E_sie, e1_sie, e2_sie, center_x_sis, center_y_sis) f_x_mst, f_y_mst = self._mst.derivatives(x, y, kappa_ext, ra_0=center_x, dec_0=center_y) f_x = lambda_mst * (f_x_sis - f_x0) + f_x_mst f_y = lambda_mst * (f_y_sis - f_y0) + f_y_mst return f_x, f_y def hessian(self, x, y, tangential_stretch, radial_stretch, curvature, dtan_dtan, direction, center_x, center_y): """ :param x: :param y: :param tangential_stretch: float, stretch of intrinsic source in tangential direction :param radial_stretch: float, stretch of intrinsic source in radial direction :param curvature: 1/curvature radius :param direction: float, angle in radian :param dtan_dtan: d(tangential_stretch) / d(tangential direction) / tangential stretch :param center_x: center of source in image plane :param center_y: center of source in image plane :return: """ lambda_mst = 1. / radial_stretch theta_E_sie, e1_sie, e2_sie, kappa_ext, center_x_sis, center_y_sis = self.stretch2sie_mst( tangential_stretch, radial_stretch, curvature, dtan_dtan, direction, center_x, center_y) f_xx_sis, f_xy_sis, f_yx_sis, f_yy_sis = self._sie.hessian( x, y, theta_E_sie, e1_sie, e2_sie, center_x_sis, center_y_sis) f_xx_mst, f_xy_mst, f_yx_mst, f_yy_mst = self._mst.hessian( x, y, kappa_ext, ra_0=center_x, dec_0=center_y) return lambda_mst * f_xx_sis + f_xx_mst, lambda_mst * f_xy_sis + f_xy_mst, lambda_mst * f_yx_sis + f_yx_mst, lambda_mst * f_yy_sis + f_yy_mst
class TestCurvedArcSISMST(object): """ tests the source model routines """ def setup(self): self.model = CurvedArcSISMST() self.sis = SIS() self.mst = Convergence() def test_spp2stretch(self): center_x, center_y = 1, 1 theta_E = 1 kappa = 0.1 center_x_spp, center_y_spp = 0., 0 tangential_stretch, radial_stretch, curvature, direction = self.model.sis_mst2stretch( theta_E, kappa, center_x_spp, center_y_spp, center_x, center_y) theta_E_new, kappa_new, center_x_spp_new, center_y_spp_new = self.model.stretch2sis_mst( tangential_stretch, radial_stretch, curvature, direction, center_x, center_y) npt.assert_almost_equal(center_x_spp_new, center_x_spp, decimal=8) npt.assert_almost_equal(center_y_spp_new, center_y_spp, decimal=8) npt.assert_almost_equal(theta_E_new, theta_E, decimal=8) npt.assert_almost_equal(kappa_new, kappa, decimal=8) center_x, center_y = -1, 1 tangential_stretch, radial_stretch, curvature, direction = self.model.sis_mst2stretch( theta_E, kappa, center_x_spp, center_y_spp, center_x, center_y) theta_E_new, kappa_new, center_x_spp_new, center_y_spp_new = self.model.stretch2sis_mst( tangential_stretch, radial_stretch, curvature, direction, center_x, center_y) npt.assert_almost_equal(center_x_spp_new, center_x_spp, decimal=8) npt.assert_almost_equal(center_y_spp_new, center_y_spp, decimal=8) npt.assert_almost_equal(theta_E_new, theta_E, decimal=8) npt.assert_almost_equal(kappa_new, kappa, decimal=8) center_x, center_y = 0, 0.5 tangential_stretch, radial_stretch, curvature, direction = self.model.sis_mst2stretch( theta_E, kappa, center_x_spp, center_y_spp, center_x, center_y) theta_E_new, kappa_new, center_x_spp_new, center_y_spp_new = self.model.stretch2sis_mst( tangential_stretch, radial_stretch, curvature, direction, center_x, center_y) npt.assert_almost_equal(center_x_spp_new, center_x_spp, decimal=8) npt.assert_almost_equal(center_y_spp_new, center_y_spp, decimal=8) npt.assert_almost_equal(theta_E_new, theta_E, decimal=8) npt.assert_almost_equal(kappa_new, kappa, decimal=8) center_x, center_y = 0, -1.5 tangential_stretch, radial_stretch, r_curvature, direction = self.model.sis_mst2stretch( theta_E, kappa, center_x_spp, center_y_spp, center_x, center_y) print(tangential_stretch, radial_stretch, r_curvature, direction) theta_E_new, kappa_new, center_x_spp_new, center_y_spp_new = self.model.stretch2sis_mst( tangential_stretch, radial_stretch, r_curvature, direction, center_x, center_y) npt.assert_almost_equal(center_x_spp_new, center_x_spp, decimal=8) npt.assert_almost_equal(center_y_spp_new, center_y_spp, decimal=8) npt.assert_almost_equal(theta_E_new, theta_E, decimal=8) npt.assert_almost_equal(kappa_new, kappa, decimal=8) def test_function(self): center_x, center_y = 0., 0. x, y = 1, 1 radial_stretch = 1 output = self.model.function(x, y, tangential_stretch=2, radial_stretch=radial_stretch, curvature=1. / 2, direction=0, center_x=center_x, center_y=center_y) theta_E, kappa_ext, center_x_sis, center_y_sis = self.model.stretch2sis_mst( tangential_stretch=2, radial_stretch=radial_stretch, curvature=1. / 2, direction=0, center_x=center_x, center_y=center_y) f_sis_out = self.sis.function( 1, 1, theta_E, center_x_sis, center_y_sis ) # - self.sis.function(0, 0, theta_E, center_x_sis, center_y_sis) alpha_x, alpha_y = self.sis.derivatives(center_x, center_y, theta_E, center_x_sis, center_y_sis) f_sis_0_out = alpha_x * (x - center_x) + alpha_y * (y - center_y) f_mst_out = self.mst.function(x, y, kappa_ext, ra_0=center_x, dec_0=center_y) lambda_mst = 1. / radial_stretch f_out = lambda_mst * (f_sis_out - f_sis_0_out) + f_mst_out npt.assert_almost_equal(output, f_out, decimal=8) def test_derivatives(self): tangential_stretch = 5 radial_stretch = 1 curvature = 1. / 10 direction = 0.3 center_x = 0 center_y = 0 x, y = 1, 1 theta_E, kappa, center_x_spp, center_y_spp = self.model.stretch2sis_mst( tangential_stretch, radial_stretch, curvature, direction, center_x, center_y) f_x_sis, f_y_sis = self.sis.derivatives(x, y, theta_E, center_x_spp, center_y_spp) f_x_mst, f_y_mst = self.mst.derivatives(x, y, kappa, ra_0=center_x, dec_0=center_y) f_x0, f_y0 = self.sis.derivatives(center_x, center_y, theta_E, center_x_spp, center_y_spp) f_x_new, f_y_new = self.model.derivatives(x, y, tangential_stretch, radial_stretch, curvature, direction, center_x, center_y) npt.assert_almost_equal(f_x_new, f_x_sis + f_x_mst - f_x0, decimal=8) npt.assert_almost_equal(f_y_new, f_y_sis + f_y_mst - f_y0, decimal=8) def test_hessian(self): lens = LensModel(lens_model_list=['CURVED_ARC_SIS_MST']) center_x, center_y = 0, 0 tangential_stretch = 10 radial_stretch = 1 kwargs_lens = [{ 'tangential_stretch': tangential_stretch, 'radial_stretch': radial_stretch, 'curvature': 1. / 10.5, 'direction': 0., 'center_x': center_x, 'center_y': center_y }] mag = lens.magnification(center_x, center_y, kwargs=kwargs_lens) npt.assert_almost_equal(mag, tangential_stretch * radial_stretch, decimal=8) center_x, center_y = 2, 3 tangential_stretch = 10 radial_stretch = 1 kwargs_lens = [{ 'tangential_stretch': tangential_stretch, 'radial_stretch': radial_stretch, 'curvature': 1. / 10.5, 'direction': 0., 'center_x': center_x, 'center_y': center_y }] mag = lens.magnification(center_x, center_y, kwargs=kwargs_lens) npt.assert_almost_equal(mag, tangential_stretch * radial_stretch, decimal=8) center_x, center_y = 0, 0 tangential_stretch = 5 radial_stretch = 1.2 kwargs_lens = [{ 'tangential_stretch': tangential_stretch, 'radial_stretch': radial_stretch, 'curvature': 1. / 10.5, 'direction': 0., 'center_x': center_x, 'center_y': center_y }] mag = lens.magnification(center_x, center_y, kwargs=kwargs_lens) npt.assert_almost_equal(mag, tangential_stretch * radial_stretch, decimal=8) center_x, center_y = 0, 0 tangential_stretch = 3 radial_stretch = -1 kwargs_lens = [{ 'tangential_stretch': tangential_stretch, 'radial_stretch': radial_stretch, 'curvature': 1. / 10.5, 'direction': 0., 'center_x': center_x, 'center_y': center_y }] mag = lens.magnification(center_x, center_y, kwargs=kwargs_lens) print(tangential_stretch, radial_stretch, 'stretches') npt.assert_almost_equal(mag, tangential_stretch * radial_stretch, decimal=8) center_x, center_y = 0, 0 tangential_stretch = -3 radial_stretch = -1 kwargs_lens = [{ 'tangential_stretch': tangential_stretch, 'radial_stretch': radial_stretch, 'curvature': 1. / 10.5, 'direction': 0., 'center_x': center_x, 'center_y': center_y }] mag = lens.magnification(center_x, center_y, kwargs=kwargs_lens) npt.assert_almost_equal(mag, tangential_stretch * radial_stretch, decimal=8) center_x, center_y = 0, 0 tangential_stretch = 10.4 radial_stretch = 0.6 kwargs_lens = [{ 'tangential_stretch': tangential_stretch, 'radial_stretch': radial_stretch, 'curvature': 1. / 10.5, 'direction': 0., 'center_x': center_x, 'center_y': center_y }] mag = lens.magnification(center_x, center_y, kwargs=kwargs_lens) npt.assert_almost_equal(mag, tangential_stretch * radial_stretch, decimal=8)
class CurvedArcSISMST(LensProfileBase): """ lens model that describes a section of a highly magnified deflector region. The parameterization is chosen to describe local observables efficient. Observables are: - curvature radius (basically bending relative to the center of the profile) - radial stretch (plus sign) thickness of arc with parity (more generalized than the power-law slope) - tangential stretch (plus sign). Infinity means at critical curve - direction of curvature - position of arc Requirements: - Should work with other perturbative models without breaking its meaning (say when adding additional shear terms) - Must best reflect the observables in lensing - minimal covariances between the parameters, intuitive parameterization. """ param_names = [ 'tangential_stretch', 'radial_stretch', 'curvature', 'direction', 'center_x', 'center_y' ] lower_limit_default = { 'tangential_stretch': -100, 'radial_stretch': -5, 'curvature': 0.000001, 'direction': -np.pi, 'center_x': -100, 'center_y': -100 } upper_limit_default = { 'tangential_stretch': 100, 'radial_stretch': 5, 'curvature': 100, 'direction': np.pi, 'center_x': 100, 'center_y': 100 } def __init__(self): self._sis = SIS() self._mst = Convergence() super(CurvedArcSISMST, self).__init__() @staticmethod def stretch2sis_mst(tangential_stretch, radial_stretch, curvature, direction, center_x, center_y): """ :param tangential_stretch: float, stretch of intrinsic source in tangential direction :param radial_stretch: float, stretch of intrinsic source in radial direction :param curvature: 1/curvature radius :param direction: float, angle in radian :param center_x: center of source in image plane :param center_y: center of source in image plane :return: parameters in terms of a spherical SIS + MST resulting in the same observables """ center_x_sis, center_y_sis = center_deflector(curvature, direction, center_x, center_y) r_curvature = 1. / curvature lambda_mst = 1. / radial_stretch kappa_ext = 1 - lambda_mst theta_E = r_curvature * (1. - radial_stretch / tangential_stretch) return theta_E, kappa_ext, center_x_sis, center_y_sis @staticmethod def sis_mst2stretch(theta_E, kappa_ext, center_x_sis, center_y_sis, center_x, center_y): """ turn Singular power-law lens model into stretch parameterization at position (center_x, center_y) This is the inverse function of stretch2spp() :param theta_E: Einstein radius of SIS profile :param kappa_ext: external convergence (MST factor 1 - kappa_ext) :param center_x_sis: center of SPP model :param center_y_sis: center of SPP model :param center_x: center of curved model definition :param center_y: center of curved model definition :return: tangential_stretch, radial_stretch, curvature, direction :return: """ r_curvature = np.sqrt((center_x_sis - center_x)**2 + (center_y_sis - center_y)**2) direction = np.arctan2(center_y - center_y_sis, center_x - center_x_sis) radial_stretch = 1. / (1 - kappa_ext) tangential_stretch = 1 / (1 - (theta_E / r_curvature)) * radial_stretch curvature = 1. / r_curvature return tangential_stretch, radial_stretch, curvature, direction def function(self, x, y, tangential_stretch, radial_stretch, curvature, direction, center_x, center_y): """ ATTENTION: there may not be a global lensing potential! :param x: :param y: :param tangential_stretch: float, stretch of intrinsic source in tangential direction :param radial_stretch: float, stretch of intrinsic source in radial direction :param curvature: 1/curvature radius :param direction: float, angle in radian :param center_x: center of source in image plane :param center_y: center of source in image plane :return: """ lambda_mst = 1. / radial_stretch theta_E, kappa_ext, center_x_sis, center_y_sis = self.stretch2sis_mst( tangential_stretch, radial_stretch, curvature, direction, center_x, center_y) f_sis = self._sis.function( x, y, theta_E, center_x_sis, center_y_sis ) # - self._sis.function(center_x, center_y, theta_E, center_x_sis, center_y_sis) alpha_x, alpha_y = self._sis.derivatives(center_x, center_y, theta_E, center_x_sis, center_y_sis) f_sis_0 = alpha_x * (x - center_x) + alpha_y * (y - center_y) f_mst = self._mst.function(x, y, kappa_ext, ra_0=center_x, dec_0=center_y) return lambda_mst * (f_sis - f_sis_0) + f_mst def derivatives(self, x, y, tangential_stretch, radial_stretch, curvature, direction, center_x, center_y): """ :param x: :param y: :param tangential_stretch: float, stretch of intrinsic source in tangential direction :param radial_stretch: float, stretch of intrinsic source in radial direction :param curvature: 1/curvature radius :param direction: float, angle in radian :param center_x: center of source in image plane :param center_y: center of source in image plane :return: """ lambda_mst = 1. / radial_stretch theta_E, kappa_ext, center_x_sis, center_y_sis = self.stretch2sis_mst( tangential_stretch, radial_stretch, curvature, direction, center_x, center_y) f_x_sis, f_y_sis = self._sis.derivatives(x, y, theta_E, center_x_sis, center_y_sis) f_x0, f_y0 = self._sis.derivatives(center_x, center_y, theta_E, center_x_sis, center_y_sis) f_x_mst, f_y_mst = self._mst.derivatives(x, y, kappa_ext, ra_0=center_x, dec_0=center_y) f_x = lambda_mst * (f_x_sis - f_x0) + f_x_mst f_y = lambda_mst * (f_y_sis - f_y0) + f_y_mst return f_x, f_y def hessian(self, x, y, tangential_stretch, radial_stretch, curvature, direction, center_x, center_y): """ :param x: :param y: :param tangential_stretch: float, stretch of intrinsic source in tangential direction :param radial_stretch: float, stretch of intrinsic source in radial direction :param curvature: 1/curvature radius :param direction: float, angle in radian :param center_x: center of source in image plane :param center_y: center of source in image plane :return: """ lambda_mst = 1. / radial_stretch theta_E, kappa_ext, center_x_sis, center_y_sis = self.stretch2sis_mst( tangential_stretch, radial_stretch, curvature, direction, center_x, center_y) f_xx_sis, f_yy_sis, f_xy_sis = self._sis.hessian( x, y, theta_E, center_x_sis, center_y_sis) f_xx_mst, f_yy_mst, f_xy_mst = self._mst.hessian(x, y, kappa_ext, ra_0=center_x, dec_0=center_y) return lambda_mst * f_xx_sis + f_xx_mst, lambda_mst * f_yy_sis + f_yy_mst, lambda_mst * f_xy_sis + f_xy_mst
class ShearReduced(LensProfileBase): """ reduced shear distortions :math:`\\gamma' = \\gamma / (1-\\kappa)`. This distortion keeps the magnification as unity and, thus, does not change the size of apparent objects. To keep the magnification at unity, it requires .. math:: (1-\\kappa)^2 - \\gamma_1^2 - \\gamma_2^ = 1 Thus, for given pair of reduced shear :math:`(\\gamma'_1, \\gamma'_2)`, an additional convergence term is calculated and added to the lensing distortions. """ param_names = ['gamma1', 'gamma2', 'ra_0', 'dec_0'] lower_limit_default = { 'gamma1': -0.5, 'gamma2': -0.5, 'ra_0': -100, 'dec_0': -100 } upper_limit_default = { 'gamma1': 0.5, 'gamma2': 0.5, 'ra_0': 100, 'dec_0': 100 } def __init__(self): self._shear = Shear() self._convergence = Convergence() super(ShearReduced, self).__init__() @staticmethod def _kappa_reduced(gamma1, gamma2): """ compute convergence such that magnification is unity :param gamma1: reduced shear :param gamma2: reduced shear :return: kappa """ kappa = 1 - 1. / np.sqrt(1 - gamma1**2 - gamma2**2) gamma1_ = (1 - kappa) * gamma1 gamma2_ = (1 - kappa) * gamma2 return kappa, gamma1_, gamma2_ def function(self, x, y, gamma1, gamma2, ra_0=0, dec_0=0): """ :param x: x-coordinate (angle) :param y: y0-coordinate (angle) :param gamma1: shear component :param gamma2: shear component :param ra_0: x/ra position where shear deflection is 0 :param dec_0: y/dec position where shear deflection is 0 :return: lensing potential """ kappa, gamma1_, gamma2_ = self._kappa_reduced(gamma1, gamma2) f_shear = self._shear.function(x, y, gamma1_, gamma2_, ra_0, dec_0) f_kappa = self._convergence.function(x, y, kappa, ra_0, dec_0) return f_shear + f_kappa def derivatives(self, x, y, gamma1, gamma2, ra_0=0, dec_0=0): """ :param x: x-coordinate (angle) :param y: y0-coordinate (angle) :param gamma1: shear component :param gamma2: shear component :param ra_0: x/ra position where shear deflection is 0 :param dec_0: y/dec position where shear deflection is 0 :return: deflection angles """ kappa, gamma1_, gamma2_ = self._kappa_reduced(gamma1, gamma2) f_x_shear, f_y_shear = self._shear.derivatives(x, y, gamma1_, gamma2_, ra_0, dec_0) f_x_kappa, f_y_kappa = self._convergence.derivatives( x, y, kappa, ra_0, dec_0) return f_x_shear + f_x_kappa, f_y_shear + f_y_kappa def hessian(self, x, y, gamma1, gamma2, ra_0=0, dec_0=0): """ :param x: x-coordinate (angle) :param y: y0-coordinate (angle) :param gamma1: shear component :param gamma2: shear component :param ra_0: x/ra position where shear deflection is 0 :param dec_0: y/dec position where shear deflection is 0 :return: f_xx, f_xy, f_yx, f_yy """ kappa, gamma1_, gamma2_ = self._kappa_reduced(gamma1, gamma2) f_xx_g, f_xy_g, f_yx_g, f_yy_g = self._shear.hessian( x, y, gamma1_, gamma2_, ra_0, dec_0) f_xx_k, f_xy_k, f_yx_k, f_yy_k = self._convergence.hessian( x, y, kappa, ra_0, dec_0) f_xx = f_xx_g + f_xx_k f_yy = f_yy_g + f_yy_k f_xy = f_xy_g + f_xy_k return f_xx, f_xy, f_xy, f_yy