class SPEMD(object): """ class for smooth power law ellipse mass density profile """ param_names = ['theta_E', 'gamma', 'e1', 'e2', 'center_x', 'center_y'] lower_limit_default = {'theta_E': 0, 'gamma': 0, 'e1': -0.5, 'e2': -0.5, 'center_x': -100, 'center_y': -100} upper_limit_default = {'theta_E': 100, 'gamma': 100, 'e1': 0.5, 'e2': 0.5, 'center_x': 100, 'center_y': 100} def __init__(self): self.s2 = 0.00000001 self.spp = SPP() self.spemd_smooth = SPEMD_SMOOTH() def function(self, x, y, theta_E, gamma, e1, e2, center_x=0, center_y=0): return self.spemd_smooth.function(x, y, theta_E, gamma, e1, e2, self.s2, center_x, center_y) def derivatives(self, x, y, theta_E, gamma, e1, e2, center_x=0, center_y=0): return self.spemd_smooth.derivatives(x, y, theta_E, gamma, e1, e2, self.s2, center_x, center_y) def hessian(self, x, y, theta_E, gamma, e1, e2, center_x=0, center_y=0): return self.spemd_smooth.hessian(x, y, theta_E, gamma, e1, e2, self.s2, center_x, center_y) def mass_3d_lens(self, r, theta_E, gamma, e1, e2): """ computes the spherical power-law mass enclosed (with SPP routiune) :param r: :param theta_E: :param gamma: :param q: :param phi_G: :return: """ return self.spp.mass_3d_lens(r, theta_E, gamma)
class SPEMD(object): """ class for smooth power law ellipse mass density profile """ def __init__(self): self.s2 = 0.00000001 self.spp = SPP() self.spemd_smooth = SPEMD_SMOOTH() def function(self, x, y, theta_E, gamma, q, phi_G, center_x=0, center_y=0): return self.spemd_smooth.function(x, y, theta_E, gamma, q, phi_G, self.s2, center_x, center_y) def derivatives(self, x, y, theta_E, gamma, q, phi_G, center_x=0, center_y=0): return self.spemd_smooth.derivatives(x, y, theta_E, gamma, q, phi_G, self.s2, center_x, center_y) def hessian(self, x, y, theta_E, gamma, q, phi_G, center_x=0, center_y=0): return self.spemd_smooth.hessian(x, y, theta_E, gamma, q, phi_G, self.s2, center_x, center_y) def mass_3d_lens(self, r, theta_E, gamma, q, phi_G): """ computes the spherical power-law mass enclosed (with SPP routiune) :param r: :param theta_E: :param gamma: :param q: :param phi_G: :return: """ return self.spp.mass_3d_lens(r, theta_E, gamma) def convert_params(self, theta_E, gamma, q): """ :param theta_E: Einstein radius :param gamma: power law slope :param q: axis ratio :return: prefactor to SPEMP profile for FASTELL """ return self.spemd_smooth.convert_params(theta_E, gamma, q)
class SPEP(LensProfileBase): """ class for Softened power-law elliptical potential (SPEP) """ param_names = ['theta_E', 'gamma', 'e1', 'e2', 'center_x', 'center_y'] lower_limit_default = { 'theta_E': 0, 'gamma': 0, 'e1': -0.5, 'e2': -0.5, 'center_x': -100, 'center_y': -100 } upper_limit_default = { 'theta_E': 100, 'gamma': 100, 'e1': 0.5, 'e2': 0.5, 'center_x': 100, 'center_y': 100 } def __init__(self): self.spp = SPP() super(SPEP, self).__init__() def function(self, x, y, theta_E, gamma, e1, e2, center_x=0, center_y=0): """ :param x: set of x-coordinates :type x: array of size (n) :param theta_E: Einstein radius of lense :type theta_E: float. :param gamma: power law slope of mass profifle :type gamma: <2 float :param e1: eccentricity :type e1: -1<e1<1 :param e2: eccentricity :type e2: -1<e1<1 :returns: function :raises: AttributeError, KeyError """ phi_G, q = param_util.ellipticity2phi_q(e1, e2) gamma, q = self._param_bounds(gamma, q) theta_E *= q x_shift = x - center_x y_shift = y - center_y E = theta_E / (((3 - gamma) / 2.)**(1. / (1 - gamma)) * np.sqrt(q)) #E = phi_E eta = -gamma + 3 xt1 = np.cos(phi_G) * x_shift + np.sin(phi_G) * y_shift xt2 = -np.sin(phi_G) * x_shift + np.cos(phi_G) * y_shift p2 = xt1**2 + xt2**2 / q**2 s2 = 0. # softening return 2 * E**2 / eta**2 * ((p2 + s2) / E**2)**(eta / 2) def derivatives(self, x, y, theta_E, gamma, e1, e2, center_x=0, center_y=0): phi_G, q = param_util.ellipticity2phi_q(e1, e2) gamma, q = self._param_bounds(gamma, q) phi_E_new = theta_E * q x_shift = x - center_x y_shift = y - center_y E = phi_E_new / (((3 - gamma) / 2.)**(1. / (1 - gamma)) * np.sqrt(q)) # E = phi_E eta = float(-gamma + 3) cos_phi = np.cos(phi_G) sin_phi = np.sin(phi_G) xt1 = cos_phi * x_shift + sin_phi * y_shift xt2 = -sin_phi * x_shift + cos_phi * y_shift xt2difq2 = xt2 / (q * q) P2 = xt1 * xt1 + xt2 * xt2difq2 if isinstance(P2, int) or isinstance(P2, float): a = max(0.000001, P2) else: a = np.empty_like(P2) p2 = P2[P2 > 0] #in the SIS regime a[P2 == 0] = 0.000001 a[P2 > 0] = p2 fac = 1. / eta * (a / (E * E))**(eta / 2 - 1) * 2 f_x_prim = fac * xt1 f_y_prim = fac * xt2difq2 f_x = cos_phi * f_x_prim - sin_phi * f_y_prim f_y = sin_phi * f_x_prim + cos_phi * f_y_prim return f_x, f_y def hessian(self, x, y, theta_E, gamma, e1, e2, center_x=0, center_y=0): phi_G, q = param_util.ellipticity2phi_q(e1, e2) gamma, q = self._param_bounds(gamma, q) phi_E_new = theta_E * q #x_shift = x - center_x #y_shift = y - center_y # shift x_ = x - center_x y_ = y - center_y # rotate x__, y__ = util.rotate(x_, y_, phi_G) E = phi_E_new / (((3 - gamma) / 2.)**(1. / (1 - gamma)) * np.sqrt(q)) if E <= 0: return np.zeros_like(x), np.zeros_like(x), np.zeros_like( x), np.zeros_like(x) # E = phi_E eta = float(-gamma + 3) #xt1 = np.cos(phi_G)*x_shift+np.sin(phi_G)*y_shift #xt2 = -np.sin(phi_G)*x_shift+np.cos(phi_G)*y_shift xt1, xt2 = x__, y__ P2 = xt1**2 + xt2**2 / q**2 if isinstance(P2, int) or isinstance(P2, float): a = max(0.000001, P2) else: a = np.empty_like(P2) p2 = P2[P2 > 0] #in the SIS regime a[P2 == 0] = 0.000001 a[P2 > 0] = p2 s2 = 0. # softening kappa = 1. / eta * (a / E**2)**(eta / 2 - 1) * ( (eta - 2) * (xt1**2 + xt2**2 / q**4) / a + (1 + 1 / q**2)) gamma1_value = 1. / eta * (a / E**2)**( eta / 2 - 1) * (1 - 1 / q**2 + (eta / 2 - 1) * (2 * xt1**2 - 2 * xt2**2 / q**4) / a) gamma2_value = 4 * xt1 * xt2 / q**2 * (1. / 2 - 1 / eta) * ( a / E**2)**(eta / 2 - 2) / E**2 gamma1 = np.cos(2 * phi_G) * gamma1_value - np.sin( 2 * phi_G) * gamma2_value gamma2 = +np.sin(2 * phi_G) * gamma1_value + np.cos( 2 * phi_G) * gamma2_value f_xx = kappa + gamma1 f_yy = kappa - gamma1 f_xy = gamma2 return f_xx, f_xy, f_xy, f_yy def mass_3d_lens(self, r, theta_E, gamma, e1=None, e2=None): """ computes the spherical power-law mass enclosed (with SPP routine) :param r: radius within the mass is computed :param theta_E: Einstein radius :param gamma: power-law slope :param e1: eccentricity component (not used) :param e2: eccentricity component (not used) :return: mass enclosed a 3D radius r """ return self.spp.mass_3d_lens(r, theta_E, gamma) def density_lens(self, r, theta_E, gamma, e1=None, e2=None): """ computes the density at 3d radius r given lens model parameterization. The integral in the LOS projection of this quantity results in the convergence quantity. :param r: radius within the mass is computed :param theta_E: Einstein radius :param gamma: power-law slope :param e1: eccentricity component (not used) :param e2: eccentricity component (not used) :return: mass enclosed a 3D radius r """ return self.spp.density_lens(r, theta_E, gamma) @staticmethod def _param_bounds(gamma, q): """ bounds parameters :param gamma: :param q: :return: """ if gamma < 1.4: gamma = 1.4 if gamma > 2.9: gamma = 2.9 if q < 0.01: q = 0.01 return float(gamma), q
class PEMD(LensProfileBase): """ class for power law ellipse mass density profile. This class effectively calls the class SPEMD_SMOOTH with a fixed and very small central smoothing scale to perform the numerical integral using the FASTELL code by Renan Barkana. .. math:: \\kappa(x, y) = \\frac{3-\\gamma}{2} \\left(\\frac{\\theta_{E}}{\\sqrt{q x^2 + y^2/q}} \\right)^{\\gamma-1} with :math:`\\theta_{E}` is the (circularized) Einstein radius, :math:`\\gamma` is the negative power-law slope of the 3D mass distributions, :math:`q` is the minor/major axis ratio, and :math:`x` and :math:`y` are defined in a coordinate system aligned with the major and minor axis of the lens. In terms of eccentricities, this profile is defined as .. math:: \\kappa(r) = \\frac{3-\\gamma}{2} \\left(\\frac{\\theta'_{E}}{r \\sqrt{1 − e*\\cos(2*\\phi)}} \\right)^{\\gamma-1} with :math:`\\epsilon` is the ellipticity defined as .. math:: \\epsilon = \\frac{1-q^2}{1+q^2} And an Einstein radius :math:`\\theta'_{\\rm E}` related to the definition used is .. math:: \\left(\\frac{\\theta'_{\\rm E}}{\\theta_{\\rm E}}\\right)^{2} = \\frac{2q}{1+q^2}. """ param_names = ['theta_E', 'gamma', 'e1', 'e2', 'center_x', 'center_y'] lower_limit_default = { 'theta_E': 0, 'gamma': 1.5, 'e1': -0.5, 'e2': -0.5, 'center_x': -100, 'center_y': -100 } upper_limit_default = { 'theta_E': 100, 'gamma': 2.5, 'e1': 0.5, 'e2': 0.5, 'center_x': 100, 'center_y': 100 } def __init__(self, suppress_fastell=False): """ :param suppress_fastell: bool, if True, does not raise if fastell4py is not installed """ self._s_scale = 0.0000001 # smoothing scale as used to numerically compute a power-law profile self.spp = SPP() self.spemd_smooth = SPEMD(suppress_fastell=suppress_fastell) super(PEMD, self).__init__() def function(self, x, y, theta_E, gamma, e1, e2, center_x=0, center_y=0): """ :param x: x-coordinate (angle) :param y: y-coordinate (angle) :param theta_E: Einstein radius (angle), pay attention to specific definition! :param gamma: logarithmic slope of the power-law profile. gamma=2 corresponds to isothermal :param e1: eccentricity component :param e2: eccentricity component :param center_x: x-position of lens center :param center_y: y-position of lens center :return: lensing potential """ return self.spemd_smooth.function(x, y, theta_E, gamma, e1, e2, self._s_scale, center_x, center_y) def derivatives(self, x, y, theta_E, gamma, e1, e2, center_x=0, center_y=0): """ :param x: x-coordinate (angle) :param y: y-coordinate (angle) :param theta_E: Einstein radius (angle), pay attention to specific definition! :param gamma: logarithmic slope of the power-law profile. gamma=2 corresponds to isothermal :param e1: eccentricity component :param e2: eccentricity component :param center_x: x-position of lens center :param center_y: y-position of lens center :return: deflection angles alpha_x, alpha_y """ return self.spemd_smooth.derivatives(x, y, theta_E, gamma, e1, e2, self._s_scale, center_x, center_y) def hessian(self, x, y, theta_E, gamma, e1, e2, center_x=0, center_y=0): """ :param x: x-coordinate (angle) :param y: y-coordinate (angle) :param theta_E: Einstein radius (angle), pay attention to specific definition! :param gamma: logarithmic slope of the power-law profile. gamma=2 corresponds to isothermal :param e1: eccentricity component :param e2: eccentricity component :param center_x: x-position of lens center :param center_y: y-position of lens center :return: Hessian components f_xx, f_xy, f_yx, f_yy """ return self.spemd_smooth.hessian(x, y, theta_E, gamma, e1, e2, self._s_scale, center_x, center_y) def mass_3d_lens(self, r, theta_E, gamma, e1=None, e2=None): """ computes the spherical power-law mass enclosed (with SPP routine) :param r: radius within the mass is computed :param theta_E: Einstein radius :param gamma: power-law slope :param e1: eccentricity component (not used) :param e2: eccentricity component (not used) :return: mass enclosed a 3D radius r """ return self.spp.mass_3d_lens(r, theta_E, gamma) def density_lens(self, r, theta_E, gamma, e1=None, e2=None): """ computes the density at 3d radius r given lens model parameterization. The integral in the LOS projection of this quantity results in the convergence quantity. :param r: radius within the mass is computed :param theta_E: Einstein radius :param gamma: power-law slope :param e1: eccentricity component (not used) :param e2: eccentricity component (not used) :return: mass enclosed a 3D radius r """ return self.spp.density_lens(r, theta_E, gamma)
class AnalyticKinematics(Anisotropy): """ class to compute eqn 20 in Suyu+2010 with a Monte-Carlo from rendering from the light profile distribution and displacing them with a Gaussian seeing convolution. This class assumes spherical symmetry in light and mass distribution and - a Hernquist light profile (parameterised by the half-light radius) - a power-law mass profile (parameterized by the Einstein radius and logarithmic slop) The analytic equations for the kinematics in this approximation are presented e.g. in Suyu et al. 2010 and the spectral rendering approach to compute the seeing convolved slit measurement is presented in Birrer et al. 2016. The stellar anisotropy is parameterised based on Osipkov 1979; Merritt 1985. All units are meant to be in angular arc seconds. The physical units are fold in through the angular diameter distances """ def __init__(self, kwargs_cosmo, interpol_grid_num=100, log_integration=False, max_integrate=100, min_integrate=0.001): """ :param kwargs_cosmo: keyword argument with angular diameter distances """ self._interp_grid_num = interpol_grid_num self._log_int = log_integration self._max_integrate = max_integrate # maximal integration (and interpolation) in units of arcsecs self._min_integrate = min_integrate # min integration (and interpolation) in units of arcsecs self._max_interpolate = max_integrate # we chose to set the interpolation range to the integration range self._min_interpolate = min_integrate # we chose to set the interpolation range to the integration range self._cosmo = Cosmo(**kwargs_cosmo) self._spp = SPP() Anisotropy.__init__(self, anisotropy_type='OM') def _rho0_r0_gamma(self, theta_E, gamma): # equation (14) in Suyu+ 2010 return -1 * math.gamma(gamma/2) / (np.sqrt(np.pi)*math.gamma((gamma-3)/2.)) * theta_E ** gamma / \ self._cosmo.arcsec2phys_lens(theta_E) * self._cosmo.epsilon_crit * const.M_sun / const.Mpc ** 3 @staticmethod def draw_light(kwargs_light): """ :param kwargs_light: keyword argument (list) of the light model :return: 3d radius (if possible), 2d projected radius, x-projected coordinate, y-projected coordinate """ if 'a' not in kwargs_light: kwargs_light['a'] = 0.551 * kwargs_light['r_eff'] a = kwargs_light['a'] r = vel_util.draw_hernquist(a) R, x, y = vel_util.project2d_random(r) return r, R, x, y def _sigma_s2(self, r, R, r_ani, a, gamma, rho0_r0_gamma): """ projected velocity dispersion :param r: 3d radius of the light tracer particle :param R: 2d projected radius of the light tracer particle :param r_ani: anisotropy radius :param a: scale of the Hernquist light profile :param gamma: power-law slope of the mass profile :param rho0_r0_gamma: combination of Einstein radius and power-law slope as equation (14) in Suyu+ 2010 :return: projected velocity dispersion """ beta = self.beta_r(r, **{'r_ani': r_ani}) return (1 - beta * R**2 / r**2) * self._sigma_r2_interp( r, a, gamma, rho0_r0_gamma, r_ani) def sigma_s2(self, r, R, kwargs_mass, kwargs_light, kwargs_anisotropy): """ returns unweighted los velocity dispersion for a specified projected radius, with weight 1 :param r: 3d radius (not needed for this calculation) :param R: 2d projected radius (in angular units of arcsec) :param kwargs_mass: mass model parameters (following lenstronomy lens model conventions) :param kwargs_light: deflector light parameters (following lenstronomy light model conventions) :param kwargs_anisotropy: anisotropy parameters, may vary according to anisotropy type chosen. We refer to the Anisotropy() class for details on the parameters. :return: line-of-sight projected velocity dispersion at projected radius R from 3d radius r """ a, gamma, rho0_r0_gamma, r_ani = self._read_out_params( kwargs_mass, kwargs_light, kwargs_anisotropy) return self._sigma_s2(r, R, r_ani, a, gamma, rho0_r0_gamma), 1 def sigma_r2(self, r, kwargs_mass, kwargs_light, kwargs_anisotropy): """ equation (19) in Suyu+ 2010 :param r: 3d radius :param kwargs_mass: mass profile keyword arguments :param kwargs_light: light profile keyword arguments :param kwargs_anisotropy: anisotropy keyword arguments :return: velocity dispersion in [m/s] """ a, gamma, rho0_r0_gamma, r_ani = self._read_out_params( kwargs_mass, kwargs_light, kwargs_anisotropy) return self._sigma_r2(r, a, gamma, rho0_r0_gamma, r_ani) def _read_out_params(self, kwargs_mass, kwargs_light, kwargs_anisotropy): """ reads the relevant parameters out of the keyword arguments and transforms them to the conventions used in this class :param kwargs_mass: mass profile keyword arguments :param kwargs_light: light profile keyword arguments :param kwargs_anisotropy: anisotropy keyword arguments :return: a (Rs of Hernquist profile), gamma, rho0_r0_gamma, r_ani """ if 'a' not in kwargs_light: kwargs_light['a'] = 0.551 * kwargs_light['r_eff'] if 'rho0_r0_gamma' not in kwargs_mass: kwargs_mass['rho0_r0_gamma'] = self._rho0_r0_gamma( kwargs_mass['theta_E'], kwargs_mass['gamma']) a = kwargs_light['a'] gamma = kwargs_mass['gamma'] rho0_r0_gamma = kwargs_mass['rho0_r0_gamma'] r_ani = kwargs_anisotropy['r_ani'] return a, gamma, rho0_r0_gamma, r_ani def _sigma_r2(self, r, a, gamma, rho0_r0_gamma, r_ani): """ equation (19) in Suyu+ 2010 """ # first term prefac1 = 4 * np.pi * const.G * a**(-gamma) * rho0_r0_gamma / (3 - gamma) prefac2 = r * (r + a)**3 / (r**2 + r_ani**2) # TODO check whether interpolation functions can speed this up hyp1 = vel_util.hyp_2F1(a=2 + gamma, b=gamma, c=3 + gamma, z=1. / (1 + r / a)) hyp2 = vel_util.hyp_2F1(a=3, b=gamma, c=1 + gamma, z=-a / r) fac = r_ani**2 / a**2 * hyp1 / ( (2 + gamma) * (r / a + 1)**(2 + gamma)) + hyp2 / (gamma * (r / a)**gamma) return prefac1 * prefac2 * fac * (const.arcsec * self._cosmo.dd * const.Mpc)**2 def _sigma_r2_interp(self, r, a, gamma, rho0_r0_gamma, r_ani): """ :param r: :param a: :param gamma: :param rho0_r0_gamma: :param r_ani: :return: """ if not hasattr(self, '_interp_sigma_r2'): min_log = np.log10(self._min_integrate) max_log = np.log10(self._max_integrate) r_array = np.logspace(min_log, max_log, self._interp_grid_num) I_R_sigma2_array = [] for r_i in r_array: I_R_sigma2_array.append( self._sigma_r2(r_i, a, gamma, rho0_r0_gamma, r_ani)) self._interp_sigma_r2 = interp1d(np.log(r_array), np.array(I_R_sigma2_array), fill_value="extrapolate") return self._interp_sigma_r2(np.log(r)) def grav_potential(self, r, kwargs_mass): """ Gravitational potential in SI units :param r: radius (arc seconds) :param kwargs_mass: :return: gravitational potential """ theta_E = kwargs_mass['theta_E'] gamma = kwargs_mass['gamma'] mass_dimless = self._spp.mass_3d_lens(r, theta_E, gamma) mass_dim = mass_dimless * const.arcsec ** 2 * self._cosmo.dd * self._cosmo.ds / self._cosmo.dds * const.Mpc * \ const.c ** 2 / (4 * np.pi * const.G) grav_pot = -const.G * mass_dim / (r * const.arcsec * self._cosmo.dd * const.Mpc) return grav_pot def delete_cache(self): """ deletes temporary cache tight to a specific model :return: """ if hasattr(self, '_interp_sigma_r2'): del self._interp_sigma_r2
class PEMD(LensProfileBase): """ class for power law ellipse mass density profile. This class effectively calls the class SPEMD_SMOOTH with a fixed and very small central smoothing scale to perform the numerical integral using the FASTELL code by Renan Barkana. The Einstein ring parameter converts to the definition used by GRAVLENS as follow: (theta_E / theta_E_gravlens) = sqrt[ (1+q^2) / (2 q) ] """ param_names = ['theta_E', 'gamma', 'e1', 'e2', 'center_x', 'center_y'] lower_limit_default = { 'theta_E': 0, 'gamma': 1.5, 'e1': -0.5, 'e2': -0.5, 'center_x': -100, 'center_y': -100 } upper_limit_default = { 'theta_E': 100, 'gamma': 2.5, 'e1': 0.5, 'e2': 0.5, 'center_x': 100, 'center_y': 100 } def __init__(self, suppress_fastell=False): """ :param suppress_fastell: bool, if True, does not raise if fastell4py is not installed """ self._s_scale = 0.0001 # smoothing scale as used to numerically compute a power-law profile self.spp = SPP() self.spemd_smooth = SPEMD(suppress_fastell=suppress_fastell) super(PEMD, self).__init__() def function(self, x, y, theta_E, gamma, e1, e2, center_x=0, center_y=0): """ :param x: x-coordinate (angle) :param y: y-coordinate (angle) :param theta_E: Einstein radius (angle), pay attention to specific definition! :param gamma: logarithmic slope of the power-law profile. gamma=2 corresponds to isothermal :param e1: eccentricity component :param e2: eccentricity component :param center_x: x-position of lens center :param center_y: y-position of lens center :return: lensing potential """ return self.spemd_smooth.function(x, y, theta_E, gamma, e1, e2, self._s_scale, center_x, center_y) def derivatives(self, x, y, theta_E, gamma, e1, e2, center_x=0, center_y=0): """ :param x: x-coordinate (angle) :param y: y-coordinate (angle) :param theta_E: Einstein radius (angle), pay attention to specific definition! :param gamma: logarithmic slope of the power-law profile. gamma=2 corresponds to isothermal :param e1: eccentricity component :param e2: eccentricity component :param center_x: x-position of lens center :param center_y: y-position of lens center :return: deflection angles alpha_x, alpha_y """ return self.spemd_smooth.derivatives(x, y, theta_E, gamma, e1, e2, self._s_scale, center_x, center_y) def hessian(self, x, y, theta_E, gamma, e1, e2, center_x=0, center_y=0): """ :param x: x-coordinate (angle) :param y: y-coordinate (angle) :param theta_E: Einstein radius (angle), pay attention to specific definition! :param gamma: logarithmic slope of the power-law profile. gamma=2 corresponds to isothermal :param e1: eccentricity component :param e2: eccentricity component :param center_x: x-position of lens center :param center_y: y-position of lens center :return: Hessian components f_xx, f_yy, f_xy """ return self.spemd_smooth.hessian(x, y, theta_E, gamma, e1, e2, self._s_scale, center_x, center_y) def mass_3d_lens(self, r, theta_E, gamma, e1=None, e2=None): """ computes the spherical power-law mass enclosed (with SPP routine) :param r: radius within the mass is computed :param theta_E: Einstein radius :param gamma: power-law slope :param e1: eccentricity component (not used) :param e2: eccentricity component (not used) :return: mass enclosed a 3D radius r """ return self.spp.mass_3d_lens(r, theta_E, gamma) def density_lens(self, r, theta_E, gamma, e1=None, e2=None): """ computes the density at 3d radius r given lens model parameterization. The integral in the LOS projection of this quantity results in the convergence quantity. :param r: radius within the mass is computed :param theta_E: Einstein radius :param gamma: power-law slope :param e1: eccentricity component (not used) :param e2: eccentricity component (not used) :return: mass enclosed a 3D radius r """ return self.spp.density_lens(r, theta_E, gamma)
class SPEP(object): """ class for Softened power-law elliptical potential (SPEP) """ def __init__(self): from lenstronomy.LensModel.Profiles.spp import SPP self.spp = SPP() def function(self, x, y, theta_E, gamma, q, phi_G, center_x=0, center_y=0): """ :param x: set of x-coordinates :type x: array of size (n) :param theta_E: Einstein radius of lense :type theta_E: float. :param gamma: power law slope of mass profifle :type gamma: <2 float :param q: Axis ratio :type q: 0<q<1 :param phi_G: position angel of SES :type q: 0<phi_G<pi/2 :returns: function :raises: AttributeError, KeyError """ gamma, q = self._param_bounds(gamma, q) theta_E *= q x_shift = x - center_x y_shift = y - center_y E = theta_E / (((3 - gamma) / 2.)**(1. / (1 - gamma)) * np.sqrt(q)) #E = phi_E eta = -gamma + 3 xt1 = np.cos(phi_G) * x_shift + np.sin(phi_G) * y_shift xt2 = -np.sin(phi_G) * x_shift + np.cos(phi_G) * y_shift p2 = xt1**2 + xt2**2 / q**2 s2 = 0. # softening return 2 * E**2 / eta**2 * ((p2 + s2) / E**2)**(eta / 2) def derivatives(self, x, y, theta_E, gamma, q, phi_G, center_x=0, center_y=0): # # @hope.jit # def xy_prime(dx, dy, eta, a, E, xt1, xt2, q): # fac = 1./eta*(a/(E*E))**(eta/2-1)*2 # dx[:] = fac*xt1 # dy[:] = fac*xt2/(q*q) gamma, q = self._param_bounds(gamma, q) phi_E_new = theta_E * q x_shift = x - center_x y_shift = y - center_y E = phi_E_new / (((3 - gamma) / 2.)**(1. / (1 - gamma)) * np.sqrt(q)) # E = phi_E eta = float(-gamma + 3) cos_phi = np.cos(phi_G) sin_phi = np.sin(phi_G) xt1 = cos_phi * x_shift + sin_phi * y_shift xt2 = -sin_phi * x_shift + cos_phi * y_shift xt2difq2 = xt2 / (q * q) P2 = xt1 * xt1 + xt2 * xt2difq2 if isinstance(P2, int) or isinstance(P2, float): a = max(0.000001, P2) else: a = np.empty_like(P2) p2 = P2[P2 > 0] #in the SIS regime a[P2 == 0] = 0.000001 a[P2 > 0] = p2 fac = 1. / eta * (a / (E * E))**(eta / 2 - 1) * 2 f_x_prim = fac * xt1 f_y_prim = fac * xt2difq2 f_x = cos_phi * f_x_prim - sin_phi * f_y_prim f_y = sin_phi * f_x_prim + cos_phi * f_y_prim return f_x, f_y def hessian(self, x, y, theta_E, gamma, q, phi_G, center_x=0, center_y=0): gamma, q = self._param_bounds(gamma, q) phi_E_new = theta_E * q x_shift = x - center_x y_shift = y - center_y E = phi_E_new / (((3 - gamma) / 2.)**(1. / (1 - gamma)) * np.sqrt(q)) # E = phi_E eta = float(-gamma + 3) xt1 = np.cos(phi_G) * x_shift + np.sin(phi_G) * y_shift xt2 = -np.sin(phi_G) * x_shift + np.cos(phi_G) * y_shift P2 = xt1**2 + xt2**2 / q**2 if isinstance(P2, int) or isinstance(P2, float): a = max(0.000001, P2) else: a = np.empty_like(P2) p2 = P2[P2 > 0] #in the SIS regime a[P2 == 0] = 0.000001 a[P2 > 0] = p2 s2 = 0. # softening kappa = 1. / eta * (a / E**2)**(eta / 2 - 1) * ( (eta - 2) * (xt1**2 + xt2**2 / q**4) / a + (1 + 1 / q**2)) gamma1_value = 1. / eta * (a / E**2)**( eta / 2 - 1) * (1 - 1 / q**2 + (eta / 2 - 1) * (2 * xt1**2 - 2 * xt2**2 / q**4) / a) gamma2_value = 4 * xt1 * xt2 / q**2 * (1. / 2 - 1 / eta) * ( a / E**2)**(eta / 2 - 2) / E**2 gamma1 = np.cos(2 * phi_G) * gamma1_value - np.sin( 2 * phi_G) * gamma2_value gamma2 = +np.sin(2 * phi_G) * gamma1_value + np.cos( 2 * phi_G) * gamma2_value f_xx = kappa + gamma1 f_yy = kappa - gamma1 f_xy = gamma2 return f_xx, f_yy, f_xy def mass_3d_lens(self, r, theta_E, gamma, q, phi_G): """ computes the spherical power-law mass enclosed (with SPP routiune) :param r: :param theta_E: :param gamma: :param q: :param phi_G: :return: """ return self.spp.mass_3d_lens(r, theta_E, gamma) def _param_bounds(self, gamma, q): """ bounds parameters :param gamma: :param q: :return: """ if gamma < 1.4: gamma = 1.4 if gamma > 2.9: gamma = 2.9 if q < 0.3: q = 0.3 return float(gamma), q
class EPL(LensProfileBase): """" Elliptical Power Law mass profile .. math:: \\kappa(x, y) = \\frac{3-\\gamma}{2} \\left(\\frac{\\theta_{E}}{\\sqrt{q x^2 + y^2/q}} \\right)^{\\gamma-1} with :math:`\\theta_{E}` is the (circularized) Einstein radius, :math:`\\gamma` is the negative power-law slope of the 3D mass distributions, :math:`q` is the minor/major axis ratio, and :math:`x` and :math:`y` are defined in a coordinate sys- tem aligned with the major and minor axis of the lens. In terms of eccentricities, this profile is defined as .. math:: \\kappa(r) = \\frac{3-\\gamma}{2} \\left(\\frac{\\theta'_{E}}{r \\sqrt{1 − e*\\cos(2*\\phi)}} \\right)^{\\gamma-1} with :math:`\\epsilon` is the ellipticity defined as .. math:: \\epsilon = \\frac{1-q^2}{1+q^2} And an Einstein radius :math:`\\theta'_{\\rm E}` related to the definition used is .. math:: \\left(\\frac{\\theta'_{\\rm E}}{\\theta_{\\rm E}}\\right)^{2} = \\frac{2q}{1+q^2}. The mathematical form of the calculation is presented by Tessore & Metcalf (2015), https://arxiv.org/abs/1507.01819. The current implementation is using hyperbolic functions. The paper presents an iterative calculation scheme, converging in few iterations to high precision and accuracy. A (faster) implementation of the same model using numba is accessible as 'EPL_NUMBA' with the iterative calculation scheme. """ param_names = ['theta_E', 'gamma', 'e1', 'e2', 'center_x', 'center_y'] lower_limit_default = { 'theta_E': 0, 'gamma': 1.5, 'e1': -0.5, 'e2': -0.5, 'center_x': -100, 'center_y': -100 } upper_limit_default = { 'theta_E': 100, 'gamma': 2.5, 'e1': 0.5, 'e2': 0.5, 'center_x': 100, 'center_y': 100 } def __init__(self): self.epl_major_axis = EPLMajorAxis() self.spp = SPP() super(EPL, self).__init__() def param_conv(self, theta_E, gamma, e1, e2): """ converts parameters as defined in this class to the parameters used in the EPLMajorAxis() class :param theta_E: Einstein radius as defined in the profile class :param gamma: negative power-law slope :param e1: eccentricity modulus :param e2: eccentricity modulus :return: b, t, q, phi_G """ if self._static is True: return self._b_static, self._t_static, self._q_static, self._phi_G_static return self._param_conv(theta_E, gamma, e1, e2) @staticmethod def _param_conv(theta_E, gamma, e1, e2): """ convert parameters from :math:`R = \\sqrt{q x^2 + y^2/q}` to :math:`R = \\sqrt{q^2 x^2 + y^2}` :param gamma: power law slope :param theta_E: Einstein radius :param e1: eccentricity component :param e2: eccentricity component :return: critical radius b, slope t, axis ratio q, orientation angle phi_G """ t = gamma - 1 phi_G, q = param_util.ellipticity2phi_q(e1, e2) b = theta_E * np.sqrt(q) return b, t, q, phi_G def set_static(self, theta_E, gamma, e1, e2, center_x=0, center_y=0): """ :param theta_E: Einstein radius :param gamma: power law slope :param e1: eccentricity component :param e2: eccentricity component :param center_x: profile center :param center_y: profile center :return: self variables set """ self._static = True self._b_static, self._t_static, self._q_static, self._phi_G_static = self._param_conv( theta_E, gamma, e1, e2) def set_dynamic(self): """ :return: """ self._static = False if hasattr(self, '_b_static'): del self._b_static if hasattr(self, '_t_static'): del self._t_static if hasattr(self, '_phi_G_static'): del self._phi_G_static if hasattr(self, '_q_static'): del self._q_static def function(self, x, y, theta_E, gamma, e1, e2, center_x=0, center_y=0): """ :param x: x-coordinate in image plane :param y: y-coordinate in image plane :param theta_E: Einstein radius :param gamma: power law slope :param e1: eccentricity component :param e2: eccentricity component :param center_x: profile center :param center_y: profile center :return: lensing potential """ b, t, q, phi_G = self.param_conv(theta_E, gamma, e1, e2) # shift x_ = x - center_x y_ = y - center_y # rotate x__, y__ = util.rotate(x_, y_, phi_G) # evaluate f_ = self.epl_major_axis.function(x__, y__, b, t, q) # rotate back return f_ def derivatives(self, x, y, theta_E, gamma, e1, e2, center_x=0, center_y=0): """ :param x: x-coordinate in image plane :param y: y-coordinate in image plane :param theta_E: Einstein radius :param gamma: power law slope :param e1: eccentricity component :param e2: eccentricity component :param center_x: profile center :param center_y: profile center :return: alpha_x, alpha_y """ b, t, q, phi_G = self.param_conv(theta_E, gamma, e1, e2) # shift x_ = x - center_x y_ = y - center_y # rotate x__, y__ = util.rotate(x_, y_, phi_G) # evaluate f__x, f__y = self.epl_major_axis.derivatives(x__, y__, b, t, q) # rotate back f_x, f_y = util.rotate(f__x, f__y, -phi_G) return f_x, f_y def hessian(self, x, y, theta_E, gamma, e1, e2, center_x=0, center_y=0): """ :param x: x-coordinate in image plane :param y: y-coordinate in image plane :param theta_E: Einstein radius :param gamma: power law slope :param e1: eccentricity component :param e2: eccentricity component :param center_x: profile center :param center_y: profile center :return: f_xx, f_xy, f_yx, f_yy """ b, t, q, phi_G = self.param_conv(theta_E, gamma, e1, e2) # shift x_ = x - center_x y_ = y - center_y # rotate x__, y__ = util.rotate(x_, y_, phi_G) # evaluate f__xx, f__xy, f__yx, f__yy = self.epl_major_axis.hessian( x__, y__, b, t, q) # rotate back kappa = 1. / 2 * (f__xx + f__yy) gamma1__ = 1. / 2 * (f__xx - f__yy) gamma2__ = f__xy gamma1 = np.cos(2 * phi_G) * gamma1__ - np.sin(2 * phi_G) * gamma2__ gamma2 = +np.sin(2 * phi_G) * gamma1__ + np.cos(2 * phi_G) * gamma2__ f_xx = kappa + gamma1 f_yy = kappa - gamma1 f_xy = gamma2 return f_xx, f_xy, f_xy, f_yy def mass_3d_lens(self, r, theta_E, gamma, e1=None, e2=None): """ computes the spherical power-law mass enclosed (with SPP routine) :param r: radius within the mass is computed :param theta_E: Einstein radius :param gamma: power-law slope :param e1: eccentricity component (not used) :param e2: eccentricity component (not used) :return: mass enclosed a 3D radius r """ return self.spp.mass_3d_lens(r, theta_E, gamma) def density_lens(self, r, theta_E, gamma, e1=None, e2=None): """ computes the density at 3d radius r given lens model parameterization. The integral in the LOS projection of this quantity results in the convergence quantity. :param r: radius within the mass is computed :param theta_E: Einstein radius :param gamma: power-law slope :param e1: eccentricity component (not used) :param e2: eccentricity component (not used) :return: mass enclosed a 3D radius r """ return self.spp.density_lens(r, theta_E, gamma)