def derivatives(self, x, y, theta_E, e1, e2, s_scale, 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 e1: eccentricity component :param e2: eccentricity component :param s_scale: smoothing scale :param center_x: profile center :param center_y: profile center :return: alpha_x, alpha_y """ b, s, q, phi_G = self.param_conv(theta_E, e1, e2, s_scale) # shift x_ = x - center_x y_ = y - center_y # rotate x__, y__ = util.rotate(x_, y_, phi_G) # evaluate f__x, f__y = self.nie_major_axis.derivatives(x__, y__, b, s, q) # rotate back f_x, f_y = util.rotate(f__x, f__y, -phi_G) return f_x, f_y
def derivatives(self, x, y, a, s, e1, e2, center_x, center_y): """ :param x: coordinate in image plane (angle) :param y: coordinate in image plane (angle) :param a: lensing strength :param s: core radius :param e1: eccentricity :param e2: eccentricity :param center_x: center of profile :param center_y: center of profile :return: deflection in x- and y-direction """ phi_q, q = param_util.ellipticity2phi_q(e1, e2) # shift x_ = x - center_x y_ = y - center_y # rotate x__, y__ = util.rotate(x_, y_, phi_q) f__x, f__y = self.major_axis_model.derivatives(x__, y__, a, s, q) # rotate deflections back f_x, f_y = util.rotate(f__x, f__y, -phi_q) return f_x, f_y
def derivatives(self, x, y, Rs, alpha_Rs, e1, e2, center_x=0, center_y=0): """ returns df/dx and df/dy of the function, calculated as an elliptically distorted deflection angle of the spherical NFW profile :param x: angular position (normally in units of arc seconds) :param y: angular position (normally in units of arc seconds) :param Rs: turn over point in the slope of the NFW profile in angular unit :param alpha_Rs: deflection (angular units) at projected Rs :param e1: eccentricity component in x-direction :param e2: eccentricity component in y-direction :param center_x: center of halo (in angular units) :param center_y: center of halo (in angular units) :return: deflection in x-direction, deflection in y-direction """ phi_q, q = param_util.ellipticity2phi_q(e1, e2) # shift x_ = x - center_x y_ = y - center_y # rotate x__, y__ = util.rotate(x_, y_, phi_q) f__x, f__y = self.cse_major_axis_set.derivatives( x__ / Rs, y__ / Rs, self._a_list, self._s_list, q) # rotate deflections back f_x, f_y = util.rotate(f__x, f__y, -phi_q) const = self._normalization(alpha_Rs, Rs, q) / Rs return const * f_x, const * f_y
def derivatives(self, x, y, theta_E, e1, e2, s_scale, center_x=0, center_y=0): """ :param x: :param y: :param theta_E: :param e1: :param e2: :param s_scale: :param center_x: :param center_y: :return: """ phi_G, q = param_util.ellipticity2phi_q(e1, e2) theta_E = self._theta_E_q_convert(theta_E, q) # shift x_ = x - center_x y_ = y - center_y # rotate x__, y__ = util.rotate(x_, y_, phi_G) # evaluate f__x, f__y = self.nie_simple.derivatives(x__, y__, theta_E, s_scale, q) # rotate back f_x, f_y = util.rotate(f__x, f__y, -phi_G) return f_x, f_y
def derivatives(self, x, y, theta_E, theta_c, e1, e2, center_x=0, center_y=0): """ :param x: x-coord (in angles) :param y: y-coord (in angles) :param theta_E: Einstein radius (in angles) :param theta_c: core radius (in angles) :param e1: eccentricity component, x direction(dimensionless) :param e2: eccentricity component, y direction (dimensionless) :return: deflection angle (in angles) """ theta_E_conv, theta_c_conv, eps, phi_G = self.param_conv(theta_E, theta_c, 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.nie_potential_major_axis.derivatives(x__, y__, theta_E_conv, theta_c_conv, eps) # 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, theta_c, e1, e2, center_x=0, center_y=0): """ :param x: x-coord (in angles) :param y: y-coord (in angles) :param theta_E: Einstein radius (in angles) :param theta_c: core radius (in angles) :param e1: eccentricity component, x direction(dimensionless) :param e2: eccentricity component, y direction (dimensionless) :return: hessian matrix (in angles) """ theta_E_conv, theta_c_conv, eps, phi_G = self.param_conv( theta_E, theta_c, e1, e2) # shift x_ = x - center_x y_ = y - center_y # rotate x__, y__ = util.rotate(x_, y_, phi_G) # evaluate f__xx, f__yy, f__xy = self.nie_potential_major_axis.hessian( x__, y__, theta_E_conv, theta_c_conv, eps) # 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_yy, f_xy
def function(self, x, y, Rs, alpha_Rs, e1, e2, center_x=0, center_y=0): """ returns elliptically distorted NFW lensing potential :param x: angular position (normally in units of arc seconds) :param y: angular position (normally in units of arc seconds) :param Rs: turn over point in the slope of the NFW profile in angular unit :param alpha_Rs: deflection (angular units) at projected Rs :param e1: eccentricity component in x-direction :param e2: eccentricity component in y-direction :param center_x: center of halo (in angular units) :param center_y: center of halo (in angular units) :return: lensing potential """ phi_q, q = param_util.ellipticity2phi_q(e1, e2) # shift x_ = x - center_x y_ = y - center_y # rotate x__, y__ = util.rotate(x_, y_, phi_q) # potential calculation f_ = self.cse_major_axis_set.function(x__ / Rs, y__ / Rs, self._a_list, self._s_list, q) const = self._normalization(alpha_Rs, Rs, q) return const * f_
def hessian(self, x, y, theta_E, e1, e2, s_scale, 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 e1: eccentricity component :param e2: eccentricity component :param s_scale: smoothing scale :param center_x: profile center :param center_y: profile center :return: f_xx, f_xy, f_yx, f_yy """ b, s, q, phi_G = self.param_conv(theta_E, e1, e2, s_scale) # shift x_ = x - center_x y_ = y - center_y # rotate x__, y__ = util.rotate(x_, y_, phi_G) # evaluate f__xx, f__xy, _, f__yy = self.nie_major_axis.hessian(x__, y__, b, s, 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 hessian(self, x, y, theta_E, e1, e2, s_scale, center_x=0, center_y=0): """ :param x: :param y: :param theta_E: :param e1: :param e2: :param s_scale: :param center_x: :param center_y: :return: """ theta_E, phi_G, q = self.param_conv(theta_E, e1, e2) # shift x_ = x - center_x y_ = y - center_y # rotate x__, y__ = util.rotate(x_, y_, phi_G) # evaluate f__xx, f__yy, f__xy = self.nie_simple.hessian(x__, y__, theta_E, s_scale, 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_yy, f_xy
def hessian(self, x, y, sigma0, Rs, e1, e2, center_x=0, center_y=0): """ returns Hessian matrix of function d^2f/dx^2, d^2/dxdy, d^2/dydx, d^f/dy^2 """ phi_q, q = param_util.ellipticity2phi_q(e1, e2) # shift x_ = x - center_x y_ = y - center_y # rotate x__, y__ = util.rotate(x_, y_, phi_q) f__xx, f__xy, __, f__yy = self.cse_major_axis_set.hessian( x__ / Rs, y__ / Rs, self._a_list, self._s_list, 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_q) * gamma1__ - np.sin(2 * phi_q) * gamma2__ gamma2 = +np.sin(2 * phi_q) * gamma1__ + np.cos(2 * phi_q) * gamma2__ f_xx = kappa + gamma1 f_yy = kappa - gamma1 f_xy = gamma2 const = self._normalization(sigma0, Rs, q) / Rs**2 return const * f_xx, const * f_xy, const * f_xy, const * f_yy
def hessian(self, x, y, a, s, e1, e2, center_x, center_y): """ :param x: coordinate in image plane (angle) :param y: coordinate in image plane (angle) :param a: lensing strength :param s: core radius :param e1: eccentricity :param e2: eccentricity :param center_x: center of profile :param center_y: center of profile :return: hessian elements f_xx, f_xy, f_yx, f_yy """ phi_q, q = param_util.ellipticity2phi_q(e1, e2) # shift x_ = x - center_x y_ = y - center_y # rotate x__, y__ = util.rotate(x_, y_, phi_q) f__xx, f__xy, __, f__yy = self.major_axis_model.hessian(x__, y__, a, s, 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_q) * gamma1__ - np.sin(2 * phi_q) * gamma2__ gamma2 = +np.sin(2 * phi_q) * gamma1__ + np.cos(2 * phi_q) * gamma2__ f_xx = kappa + gamma1 f_yy = kappa - gamma1 f_xy = gamma2 return f_xx, f_xy, f_xy, f_yy
def derivatives(self, x, y, sigma0, Rs, e1, e2, center_x=0, center_y=0): """ returns df/dx and df/dy of the function (integral of NFW) """ phi_q, q = param_util.ellipticity2phi_q(e1, e2) # shift x_ = x - center_x y_ = y - center_y # rotate x__, y__ = util.rotate(x_, y_, phi_q) f__x, f__y = self.cse_major_axis_set.derivatives( x__ / Rs, y__ / Rs, self._a_list, self._s_list, q) # rotate deflections back f_x, f_y = util.rotate(f__x, f__y, -phi_q) const = self._normalization(sigma0, Rs, q) / Rs return const * f_x, const * f_y
def derivatives(self, x, y, tangential_stretch, curvature, direction, center_x, center_y): """ :param x: :param y: :param tangential_stretch: float, stretch of intrinsic source in tangential 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: """ r = 1 / curvature # deflection angle to allow for tangential stretch # (ratio of source position around zero point relative to radius is tangential stretch) alpha = r * (1 / tangential_stretch + 1) # shift x_ = x - center_x y_ = y - center_y # rotate x__, y__ = util.rotate(x_, y_, direction) # evaluate # move x-coordinate to circle intercept with x-axis if isinstance(x, int) or isinstance(x, float): if abs(y__) > r: f__x, f__y = 0, 0 else: f__x, f__y = self._deflection(y__, r, tangential_stretch) else: f__x, f__y = np.zeros_like(x__), np.zeros_like(y__) _y__ = y__[y__ <= r] _f__x, _f__y = self._deflection(_y__, r, tangential_stretch) f__x[y__ <= r] = _f__x f__y[y__ <= r] = _f__y # rotate back f_x, f_y = util.rotate(f__x, f__y, -direction) return f_x, f_y
def magnification_finite(self, x_pos, y_pos, kwargs_lens, source_sigma=0.003, window_size=0.1, grid_number=100, polar_grid=False, aspect_ratio=0.5): """ returns the magnification of an extended source with Gaussian light profile :param x_pos: x-axis positons of point sources :param y_pos: y-axis position of point sources :param kwargs_lens: lens model kwargs :param source_sigma: Gaussian sigma in arc sec in source :param window_size: size of window to compute the finite flux :param grid_number: number of grid cells per axis in the window to numerically compute the flux :return: numerically computed brightness of the sources """ mag_finite = np.zeros_like(x_pos) deltaPix = float(window_size) / grid_number from lenstronomy.LightModel.Profiles.gaussian import Gaussian quasar = Gaussian() x_grid, y_grid = util.make_grid(numPix=grid_number, deltapix=deltaPix, subgrid_res=1) if polar_grid is True: a = window_size * 0.5 b = window_size * 0.5 * aspect_ratio ellipse_inds = (x_grid * a**-1)**2 + (y_grid * b**-1)**2 <= 1 x_grid, y_grid = x_grid[ellipse_inds], y_grid[ellipse_inds] for i in range(len(x_pos)): ra, dec = x_pos[i], y_pos[i] center_x, center_y = self._lensModel.ray_shooting( ra, dec, kwargs_lens) if polar_grid is True: theta = np.arctan2(dec, ra) xcoord, ycoord = util.rotate(x_grid, y_grid, theta) else: xcoord, ycoord = x_grid, y_grid betax, betay = self._lensModel.ray_shooting( xcoord + ra, ycoord + dec, kwargs_lens) I_image = quasar.function(betax, betay, 1., source_sigma, center_x, center_y) mag_finite[i] = np.sum(I_image) * deltaPix**2 return mag_finite
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 function(self, x, y, sigma0, Rs, e1, e2, center_x=0, center_y=0): """ returns double integral of NFW profile """ phi_q, q = param_util.ellipticity2phi_q(e1, e2) # shift x_ = x - center_x y_ = y - center_y # rotate x__, y__ = util.rotate(x_, y_, phi_q) # potential calculation f_ = self.cse_major_axis_set.function(x__ / Rs, y__ / Rs, self._a_list, self._s_list, q) const = self._normalization(sigma0, Rs, q) return const * f_
def coord2image_pixel(ra, dec, center_x, center_y, phi_G, scale): """ :param ra: angular coordinate :param dec: angular coordinate :param center_x: center of image in angular coordinates :param center_y: center of image in angular coordinates :param phi_G: rotation angle :param scale: pixel scale of image :return: pixel coordinates """ ra_ = ra - center_x dec_ = dec - center_y x_ = ra_ / scale y_ = dec_ / scale x, y = util.rotate(x_, y_, phi_G) return x, y
def mask_ellipse(x, y, center_x, center_y, a, b, angle): """ :param x: x-coordinates of pixels :param y: y-coordinates of pixels :param center_x: center of mask :param center_y: center of mask :param a: major axis :param b: minor axis :param angle: angle of major axis :return: mask (list of zeros and ones) """ x_shift = x - center_x y_shift = y - center_y x_rot, y_rot = util.rotate(x_shift, y_shift, angle) r_ab = x_rot**2 / a**2 + y_rot**2 / b**2 mask = np.empty_like(r_ab) mask[r_ab > 1] = 0 mask[r_ab <= 1] = 1 return mask
def function(self, x, y, amp, e1, e2, s_scale, center_x=0, center_y=0): """ :param x: x-coordinate :param y: y-coordinate :param amp: surface brightness normalization :param e1: eccentricity component :param e2: eccentricity component :param s_scale: smoothing scale (square averaged of minor and major axis) :param center_x: center of profile :param center_y: center of profile :return: surface brightness of NIE profile """ x_ = x - center_x y_ = y - center_y phi_G, q = param_util.ellipticity2phi_q(e1, e2) # rotate x__, y__ = util.rotate(x_, y_, phi_G) s = s_scale * np.sqrt((1 + q ** 2) / (2 * q ** 2)) f_ = amp/2. * (q**2 * (s**2 + x__**2) + y__**2)**(-1./2) return f_
def function(self, x, y, a, s, e1, e2, center_x, center_y): """ :param x: coordinate in image plane (angle) :param y: coordinate in image plane (angle) :param a: lensing strength :param s: core radius :param e1: eccentricity :param e2: eccentricity :param center_x: center of profile :param center_y: center of profile :return: lensing potential """ phi_q, q = param_util.ellipticity2phi_q(e1, e2) # shift x_ = x - center_x y_ = y - center_y # rotate x__, y__ = util.rotate(x_, y_, phi_q) # potential calculation f_ = self.major_axis_model.function(x__, y__, a, s, q) return f_
def function(self, x, y, amp, e1, e2, s_scale, center_x=0, center_y=0): """ :param x: :param y: :param theta_E: :param e1: :param e2: :param s_scale: :param center_x: :param center_y: :return: """ phi_G, q = param_util.ellipticity2phi_q(e1, e2) # shift x_ = x - center_x y_ = y - center_y # rotate x__, y__ = util.rotate(x_, y_, phi_G) # evaluate f_ = self._nie_simple_function(x__, y__, amp, s_scale, q) # rotate back return f_
def function(self, x, y, theta_E, e1, e2, s_scale, center_x=0, center_y=0): """ :param x: :param y: :param theta_E: :param e1: :param e2: :param s_scale: :param center_x: :param center_y: :return: """ theta_E, phi_G, q = self.param_conv(theta_E, e1, e2) # shift x_ = x - center_x y_ = y - center_y # rotate x__, y__ = util.rotate(x_, y_, phi_G) # evaluate f_ = self.nie_simple.function(x__, y__, theta_E, s_scale, q) # rotate back return f_
def function(self, x, y, theta_E, e1, e2, s_scale, 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 e1: eccentricity component :param e2: eccentricity component :param s_scale: smoothing scale :param center_x: profile center :param center_y: profile center :return: lensing potential """ b, s, q, phi_G = self.param_conv(theta_E, e1, e2, s_scale) # shift x_ = x - center_x y_ = y - center_y # rotate x__, y__ = util.rotate(x_, y_, phi_G) # evaluate f_ = self.nie_major_axis.function(x__, y__, b, s, q) # rotate back return f_
def hessian(self, x, y, Rs, alpha_Rs, e1, e2, center_x=0, center_y=0): """ returns Hessian matrix of function d^2f/dx^2, d^f/dy^2, d^2/dxdy the calculation is performed as a numerical differential from the deflection field. Analytical relations are possible. :param x: angular position (normally in units of arc seconds) :param y: angular position (normally in units of arc seconds) :param Rs: turn over point in the slope of the NFW profile in angular unit :param alpha_Rs: deflection (angular units) at projected Rs :param e1: eccentricity component in x-direction :param e2: eccentricity component in y-direction :param center_x: center of halo (in angular units) :param center_y: center of halo (in angular units) :return: d^2f/dx^2, d^2/dxdy, d^2/dydx, d^f/dy^2 """ phi_q, q = param_util.ellipticity2phi_q(e1, e2) # shift x_ = x - center_x y_ = y - center_y # rotate x__, y__ = util.rotate(x_, y_, phi_q) f__xx, f__xy, __, f__yy = self.cse_major_axis_set.hessian( x__ / Rs, y__ / Rs, self._a_list, self._s_list, 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_q) * gamma1__ - np.sin(2 * phi_q) * gamma2__ gamma2 = +np.sin(2 * phi_q) * gamma1__ + np.cos(2 * phi_q) * gamma2__ f_xx = kappa + gamma1 f_yy = kappa - gamma1 f_xy = gamma2 const = self._normalization(alpha_Rs, Rs, q) / Rs**2 return const * f_xx, const * f_xy, const * f_xy, const * f_yy
def test_derivatives(self): x = np.array([1]) y = np.array([2]) mu_r = 1. mu_t = 10. # positive parity parity = 1 ############ # rotation 1 ############ phi_G = np.pi f_x, f_y = self.const_mag.derivatives(x, y, mu_r, mu_t, parity, phi_G) # rotate x__, y__ = util.rotate(x, y, phi_G) # evaluate f__x, f__y = self.const_mag.derivatives(x__, y__, mu_r, mu_t, parity, 0.0) # rotate back f_x_rot, f_y_rot = util.rotate(f__x, f__y, -phi_G) # compare npt.assert_almost_equal(f_x, f_x_rot, decimal=4) npt.assert_almost_equal(f_y, f_y_rot, decimal=4) ############ # rotation 2 ############ phi_G = np.pi / 3. f_x, f_y = self.const_mag.derivatives(x, y, mu_r, mu_t, parity, phi_G) # rotate x__, y__ = util.rotate(x, y, phi_G) # evaluate f__x, f__y = self.const_mag.derivatives(x__, y__, mu_r, mu_t, parity, 0.0) # rotate back f_x_rot, f_y_rot = util.rotate(f__x, f__y, -phi_G) # compare npt.assert_almost_equal(f_x, f_x_rot, decimal=4) npt.assert_almost_equal(f_y, f_y_rot, decimal=4) #=========================================================== # negative parity parity = -1 ############ # rotation 1 ############ phi_G = np.pi f_x, f_y = self.const_mag.derivatives(x, y, mu_r, mu_t, parity, phi_G) # rotate x__, y__ = util.rotate(x, y, phi_G) # evaluate f__x, f__y = self.const_mag.derivatives(x__, y__, mu_r, mu_t, parity, 0.0) # rotate back f_x_rot, f_y_rot = util.rotate(f__x, f__y, -phi_G) # compare npt.assert_almost_equal(f_x, f_x_rot, decimal=4) npt.assert_almost_equal(f_y, f_y_rot, decimal=4) ############ # rotation 2 ############ phi_G = np.pi / 3. f_x, f_y = self.const_mag.derivatives(x, y, mu_r, mu_t, parity, phi_G) # rotate x__, y__ = util.rotate(x, y, phi_G) # evaluate f__x, f__y = self.const_mag.derivatives(x__, y__, mu_r, mu_t, parity, 0.0) # rotate back f_x_rot, f_y_rot = util.rotate(f__x, f__y, -phi_G) # compare npt.assert_almost_equal(f_x, f_x_rot, decimal=4) npt.assert_almost_equal(f_y, f_y_rot, decimal=4)
def magnification_finite_adaptive(self, x_image, y_image, source_x, source_y, kwargs_lens, source_fwhm_parsec, z_source, cosmo=None, grid_resolution=None, grid_radius_arcsec=None, axis_ratio=0.5, tol=0.001, step_size=0.05, use_largest_eigenvalue=True, source_light_model='SINGLE_GAUSSIAN', dx=None, dy=None, size_scale=None, amp_scale=None, fixed_aperture_size=False): """ This method computes image magnifications with a finite-size background source assuming a Gaussian or a double Gaussian source light profile. It can be much faster that magnification_finite for lens models with many deflectors and a compact source. This is because most pixels in a rectangular window around a lensed image of a compact source do not map onto the source, and therefore don't contribute to the integrated flux in the image plane. Rather than ray tracing through a rectangular grid, this routine accelerates the computation of image magnifications with finite-size sources by ray tracing through an elliptical region oriented such that tracks the surface brightness of the lensed image. The aperture size is initially quite small, and increases in size until the flux inside of it (and hence the magnification) converges. The orientation of the elliptical aperture is computed from the magnification tensor evaluated at the image coordinate. If for whatever reason you prefer a circular aperture to the elliptical approximation using the hessian eigenvectors, you can just set axis_ratio = 1. To use the eigenvalues of the hessian matrix to estimate the optimum axis ratio, set axis_ratio = 0. The default settings for the grid resolution and ray tracing window size work well for sources with fwhm between 0.5 - 100 pc. :param x_image: a list or array of x coordinates [units arcsec] :param y_image: a list or array of y coordinates [units arcsec] :param source_x: float, source position :param source_y: float, source position :param kwargs_lens: keyword arguments for the lens model :param source_fwhm_parsec: the size of the background source [units parsec] :param z_source: the source redshift :param cosmo: (optional) an instance of astropy.cosmology; if not specified, a default cosmology will be used :param grid_resolution: the grid resolution in units arcsec/pixel; if not specified, an appropriate value will be estimated from the source size :param grid_radius_arcsec: (optional) the size of the ray tracing region in arcsec; if not specified, an appropriate value will be estimated from the source size :param axis_ratio: the axis ratio of the ellipse used for ray tracing; if axis_ratio = 0, then the eigenvalues the hessian matrix will be used to estimate an appropriate axis ratio. Be warned: if the image is highly magnified it will tend to curve out of the resulting ellipse :param tol: tolerance for convergence in the magnification :param step_size: sets the increment for the successively larger ray tracing windows :param use_largest_eigenvalue: bool; if True, then the major axis of the ray tracing ellipse region will be aligned with the eigenvector corresponding to the largest eigenvalue of the hessian matrix :param source_light_model: the model for backgourn source light; currently implemented are 'SINGLE_GAUSSIAN' and 'DOUBLE_GAUSSIAN'. :param dx: used with source model 'DOUBLE_GAUSSIAN', the offset of the second source light profile from the first [arcsec] :param dy: used with source model 'DOUBLE_GAUSSIAN', the offset of the second source light profile from the first [arcsec] :param size_scale: used with source model 'DOUBLE_GAUSSIAN', the size of the second source light profile relative to the first :param amp_scale: used with source model 'DOUBLE_GAUSSIAN', the peak brightness of the second source light profile relative to the first :param fixed_aperture_size: bool, if True the flux is computed inside a fixed aperture size with radius grid_radius_arcsec :return: an array of image magnifications """ grid_x_0, grid_y_0, source_model, kwargs_source, grid_resolution, grid_radius_arcsec = setup_mag_finite(cosmo, self._lensModel, grid_radius_arcsec, grid_resolution, source_fwhm_parsec, source_light_model, z_source, source_x, source_y, dx, dy, amp_scale, size_scale) grid_x_0, grid_y_0 = grid_x_0.ravel(), grid_y_0.ravel() minimum_magnification = 1e-5 magnifications = [] for xi, yi in zip(x_image, y_image): if axis_ratio == 1: grid_r = np.hypot(grid_x_0, grid_y_0) else: w1, w2, v11, v12, v21, v22 = self.hessian_eigenvectors(xi, yi, kwargs_lens) _v = [np.array([v11, v12]), np.array([v21, v22])] _w = [abs(w1), abs(w2)] if use_largest_eigenvalue: idx = int(np.argmax(_w)) else: idx = int(np.argmin(_w)) v = _v[idx] rotation_angle = np.arctan(v[1] / v[0]) - np.pi / 2 grid_x, grid_y = util.rotate(grid_x_0, grid_y_0, rotation_angle) if axis_ratio == 0: sort = np.argsort(_w) q = _w[sort[0]] / _w[sort[1]] grid_r = np.hypot(grid_x, grid_y / q).ravel() else: grid_r = np.hypot(grid_x, grid_y / axis_ratio).ravel() flux_array = np.zeros_like(grid_x_0) step = step_size * grid_radius_arcsec r_min = 0 if fixed_aperture_size: r_max = grid_radius_arcsec else: r_max = step magnification_current = 0. while True: flux_array = self._magnification_adaptive_iteration(flux_array, xi, yi, grid_x_0, grid_y_0, grid_r, r_min, r_max, self._lensModel, kwargs_lens, source_model, kwargs_source) new_magnification = np.sum(flux_array) * grid_resolution ** 2 diff = abs(new_magnification - magnification_current) / new_magnification if r_max >= grid_radius_arcsec: break elif diff < tol and new_magnification > minimum_magnification: break else: r_min += step r_max += step magnification_current = new_magnification magnifications.append(new_magnification) return np.array(magnifications)
def test_function(self): y = np.array([1., 2]) x = np.array([0., 0.]) mu_r = 1. mu_t = 10. # positive parity parity = 1 ############ # rotation 1 ############ phi_G = np.pi values = self.const_mag.function(x, y, mu_r, mu_t, parity, phi_G) delta_pot = values[1] - values[0] # rotate x__, y__ = util.rotate(x, y, phi_G) # evaluate f_ = self.const_mag.function(x__, y__, mu_r, mu_t, parity, 0.0) # rotate back delta_pot_rot = f_[1] - f_[0] # compare npt.assert_almost_equal(delta_pot, delta_pot_rot, decimal=4) ############ # rotation 2 ############ phi_G = np.pi / 3. values = self.const_mag.function(x, y, mu_r, mu_t, parity, phi_G) delta_pot = values[1] - values[0] # rotate x__, y__ = util.rotate(x, y, phi_G) # evaluate f_ = self.const_mag.function(x__, y__, mu_r, mu_t, parity, 0.0) # rotate back delta_pot_rot = f_[1] - f_[0] # compare npt.assert_almost_equal(delta_pot, delta_pot_rot, decimal=4) #=========================================================== # negative parity parity = -1 ############ # rotation 1 ############ phi_G = np.pi values = self.const_mag.function(x, y, mu_r, mu_t, parity, phi_G) delta_pot = values[1] - values[0] # rotate x__, y__ = util.rotate(x, y, phi_G) # evaluate f_ = self.const_mag.function(x__, y__, mu_r, mu_t, parity, 0.0) # rotate back delta_pot_rot = f_[1] - f_[0] # compare npt.assert_almost_equal(delta_pot, delta_pot_rot, decimal=4) ############ # rotation 2 ############ phi_G = np.pi / 3. values = self.const_mag.function(x, y, mu_r, mu_t, parity, phi_G) delta_pot = values[1] - values[0] # rotate x__, y__ = util.rotate(x, y, phi_G) # evaluate f_ = self.const_mag.function(x__, y__, mu_r, mu_t, parity, 0.0) # rotate back delta_pot_rot = f_[1] - f_[0] # compare npt.assert_almost_equal(delta_pot, delta_pot_rot, decimal=4)
def test_hessian(self): x = np.array([1]) y = np.array([2]) mu_r = 1. mu_t = 10. # positive parity parity = 1 ############ # rotation 1 ############ phi_G = np.pi f_xx, f_xy, f_yx, f_yy = self.const_mag.hessian( x, y, mu_r, mu_t, parity, phi_G) # rotate x__, y__ = util.rotate(x, y, phi_G) # evaluate f__xx, f__xy, f__yx, f__yy = self.const_mag.hessian( x__, y__, mu_r, mu_t, parity, 0.0) # 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_rot = kappa + gamma1 f_yy_rot = kappa - gamma1 f_xy_rot = gamma2 # compare npt.assert_almost_equal(f_xx, f_xx_rot, decimal=4) npt.assert_almost_equal(f_yy, f_yy_rot, decimal=4) npt.assert_almost_equal(f_xy, f_xy_rot, decimal=4) npt.assert_almost_equal(f_xy, f_yx, decimal=8) ############ # rotation 2 ############ phi_G = np.pi / 3. f_xx, f_xy, f_yx, f_yy = self.const_mag.hessian( x, y, mu_r, mu_t, parity, phi_G) # rotate x__, y__ = util.rotate(x, y, phi_G) # evaluate f__xx, f__xy, f__yx, f__yy = self.const_mag.hessian( x__, y__, mu_r, mu_t, parity, 0.0) # 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_rot = kappa + gamma1 f_yy_rot = kappa - gamma1 f_xy_rot = gamma2 # compare npt.assert_almost_equal(f_xx, f_xx_rot, decimal=4) npt.assert_almost_equal(f_yy, f_yy_rot, decimal=4) npt.assert_almost_equal(f_xy, f_xy_rot, decimal=4) npt.assert_almost_equal(f_yx, f_xy_rot, decimal=4)
def magnification_finite_adaptive(self, x_image, y_image, source_x, source_y, kwargs_lens, source_fwhm_parsec, z_source, cosmo=None, grid_resolution=None, grid_radius_arcsec=None, axis_ratio=0.5, tol=0.001, step_size=0.05, use_largest_eigenvalue=True): """ This method computes image magnifications with a finite-size background source assuming a Gaussian source light profile. It can be much faster that magnification_finite for lens models with many deflectors and a relatively compact source. This is because most pixels in a rectangular window around a lensed image of a compact source will contain zero flux, and therefore don't contribute to the image brightness. Rather than ray tracing through a rectangular grid, this routine accelerates the computation of image magnifications with finite-size sources by ray tracing through an elliptical aperture oriented such that it resembles the surface brightness of the lensed image itself. The aperture size is initially quite small, and increases in size until the flux inside of it (and hence the magnification) converges. The orientation of the elliptical aperture is computed from the magnification tensor at the image coordinate. If for whatever reason you prefer a circular aperture to the elliptical approximation using the hessian eigenvectors, you can just set axis_ratio = 1. To use the eigenvalues of the hessian matrix to estimate the optimum axis ratio, set axis_ratio = 0. The default settings for the grid resolution and ray tracing window size work well for sources with fwhm between 0.5 - 100 pc. :param x_image: a list or array of x coordinates [units arcsec] :param y_image: a list or array of y coordinates [units arcsec] :param kwargs_lens: keyword arguments for the lens model :param source_fwhm_parsec: the size of the background source [units parsec] :param z_source: the source redshift :param cosmo: (optional) an instance of astropy.cosmology; if not specified, a default cosmology will be used :param grid_resolution: the grid resolution in units arcsec/pixel; if not specified, an appropriate value will be estimated from the source size :param grid_radius_arcsec: (optional) the size of the ray tracing region in arcsec; if not specified, an appropriate value will be estimated from the source size :param axis_ratio: the axis ratio of the ellipse used for ray tracing; if axis_ratio = 0, then the eigenvalues the hessian matrix will be used to estimate an appropriate axis ratio. Be warned: if the image is highly magnified it will tend to curve out of the resulting ellipse :param tol: tolerance for convergence in the magnification :param step_size: sets the increment for the successively larger ray tracing windows :param use_largest_eigenvalue: bool; if True, then the major axis of the ray tracing ellipse region will be aligned with the eigenvector corresponding to the largest eigenvalue of the hessian matrix :return: an array of image magnifications """ if cosmo is None: cosmo = self._lensModel.cosmo if grid_radius_arcsec is None: grid_radius_arcsec = auto_raytracing_grid_size(source_fwhm_parsec) if grid_resolution is None: grid_resolution = auto_raytracing_grid_resolution( source_fwhm_parsec) pc_per_arcsec = 1000 / cosmo.arcsec_per_kpc_proper(z_source).value source_fwhm_arcsec = source_fwhm_parsec / pc_per_arcsec source_sigma_arcsec = fwhm2sigma(source_fwhm_arcsec) kwargs_source = [{ 'amp': 1., 'center_x': source_x, 'center_y': source_y, 'sigma': source_sigma_arcsec }] source_model = LightModel(['GAUSSIAN']) npix = int(2 * grid_radius_arcsec / grid_resolution) _grid_x = np.linspace(-grid_radius_arcsec, grid_radius_arcsec, npix) _grid_y = np.linspace(-grid_radius_arcsec, grid_radius_arcsec, npix) magnifications = [] minimum_magnification = 1e-4 grid_x_0, grid_y_0 = np.meshgrid(_grid_x, _grid_y) grid_x_0, grid_y_0 = grid_x_0.ravel(), grid_y_0.ravel() for xi, yi in zip(x_image, y_image): w1, w2, v11, v12, v21, v22 = self.hessian_eigenvectors( xi, yi, kwargs_lens) _v = [np.array([v11, v12]), np.array([v21, v22])] _w = [abs(w1), abs(w2)] if use_largest_eigenvalue: idx = int(np.argmax(_w)) else: idx = int(np.argmin(_w)) v = _v[idx] rotation_angle = np.arctan(v[1] / v[0]) - np.pi / 2 grid_x, grid_y = util.rotate(grid_x_0, grid_y_0, rotation_angle) if axis_ratio == 0: sort = np.argsort(_w) q = _w[sort[0]] / _w[sort[1]] grid_r = np.hypot(grid_x, grid_y / q).ravel() else: grid_r = np.hypot(grid_x, grid_y / axis_ratio).ravel() flux_array = np.zeros_like(grid_x_0) step = step_size * grid_radius_arcsec r_min = 0 r_max = step magnification_current = 0. while True: flux_array = self._magnification_adaptive_iteration( flux_array, xi, yi, grid_x_0, grid_y_0, grid_r, r_min, r_max, self._lensModel, kwargs_lens, source_model, kwargs_source) new_magnification = np.sum(flux_array) * grid_resolution**2 diff = abs(new_magnification - magnification_current) / new_magnification # the sqrt(2) will allow this algorithm to fill up the entire square window if r_max > np.sqrt(2) * grid_radius_arcsec: break elif diff < tol and new_magnification > minimum_magnification: break else: r_min += step r_max += step magnification_current = new_magnification magnifications.append(new_magnification) return np.array(magnifications)