class GaussianEllipseKappaSet(object): """ This class computes the lensing properties of a set of concentric elliptical Gaussian convergences. """ param_names = ['amp', 'sigma', 'e1', 'e2', 'center_x', 'center_y'] lower_limit_default = { 'amp': 0, 'sigma': 0, 'e1': -0.5, 'e2': -0.5, 'center_x': -100, 'center_y': -100 } upper_limit_default = { 'amp': 100, 'sigma': 100, 'e1': 0.5, 'e2': 0.5, 'center_x': 100, 'center_y': 100 } def __init__(self, use_scipy_wofz=True, min_ellipticity=1e-5): """ :param use_scipy_wofz: To initiate ``class GaussianEllipseKappa``. If ``True``, Gaussian lensing will use ``scipy.special.wofz`` function. Set ``False`` for lower precision, but faster speed. :type use_scipy_wofz: ``bool`` :param min_ellipticity: To be passed to ``class GaussianEllipseKappa``. Minimum ellipticity for Gaussian elliptical lensing calculation. For lower ellipticity than min_ellipticity the equations for the spherical case will be used. :type min_ellipticity: ``float`` """ self.gaussian_ellipse_kappa = GaussianEllipseKappa( use_scipy_wofz=use_scipy_wofz, min_ellipticity=min_ellipticity) def function(self, x, y, amp, sigma, e1, e2, center_x=0, center_y=0): """ Compute the potential function for a set of concentric elliptical Gaussian convergence profiles. :param x: x coordinate :type x: ``float`` or ``numpy.array`` :param y: y coordinate :type y: ``float`` or ``numpy.array`` :param amp: Amplitude of Gaussian, convention: :math:`A/(2 \pi\sigma^2) \exp(-(x^2+y^2/q^2)/2\sigma^2)` :type amp: ``numpy.array`` with ``dtype=float`` :param sigma: Standard deviation of Gaussian :type sigma: ``numpy.array`` with ``dtype=float`` :param e1: Ellipticity parameter 1 :type e1: ``float`` :param e2: Ellipticity parameter 2 :type e2: ``float`` :param center_x: x coordinate of centroid :type center_x: ``float`` :param center_y: y coordianate of centroid :type center_y: ``float`` :return: Potential for elliptical Gaussian convergence :rtype: ``float``, or ``numpy.array`` with ``shape = x.shape`` """ function = np.zeros_like(x, dtype=float) for i in range(len(amp)): function += self.gaussian_ellipse_kappa.function( x, y, amp[i], sigma[i], e1, e2, center_x, center_y) return function def derivatives(self, x, y, amp, sigma, e1, e2, center_x=0, center_y=0): """ Compute the derivatives of function angles :math:`\partial f/\partial x`, :math:`\partial f/\partial y` at :math:`x,\ y` for a set of concentric elliptic Gaussian convergence profiles. :param x: x coordinate :type x: ``float`` or ``numpy.array`` :param y: y coordinate :type y: ``float`` or ``numpy.array`` :param amp: Amplitude of Gaussian, convention: :math:`A/(2 \pi\sigma^2) \exp(-(x^2+y^2/q^2)/2\sigma^2)` :type amp: ``numpy.array`` with ``dtype=float`` :param sigma: Standard deviation of Gaussian :type sigma: ``numpy.array`` with ``dtype=float`` :param e1: Ellipticity parameter 1 :type e1: ``float`` :param e2: Ellipticity parameter 2 :type e2: ``float`` :param center_x: x coordinate of centroid :type center_x: ``float`` :param center_y: y coordianate of centroid :type center_y: ``float`` :return: Deflection angle :math:`\partial f/\partial x`, :math:`\partial f/\partial y` for elliptical Gaussian convergence :rtype: tuple ``(float, float)`` or ``(numpy.array, numpy.array)`` with each ``numpy`` array's shape equal to ``x.shape`` """ f_x = np.zeros_like(x, dtype=float) f_y = np.zeros_like(x, dtype=float) for i in range(len(amp)): f_x_i, f_y_i = self.gaussian_ellipse_kappa.derivatives( x, y, amp=amp[i], sigma=sigma[i], e1=e1, e2=e2, center_x=center_x, center_y=center_y) f_x += f_x_i f_y += f_y_i return f_x, f_y def hessian(self, x, y, amp, sigma, e1, e2, center_x=0, center_y=0): """ Compute Hessian matrix of function :math:`\partial^2f/\partial x^2`, :math:`\partial^2 f/\partial y^2`, :math:`\partial^2 f/\partial x\partial y` for a set of concentric elliptic Gaussian convergence profiles. :param x: x coordinate :type x: ``float`` or ``numpy.array`` :param y: y coordinate :type y: ``float`` or ``numpy.array`` :param amp: Amplitude of Gaussian, convention: :math:`A/(2 \pi\sigma^2) \exp(-(x^2+y^2/q^2)/2\sigma^2)` :type amp: ``numpy.array`` with ``dtype=float`` :param sigma: Standard deviation of Gaussian :type sigma: ``numpy.array`` with ``dtype=float`` :param e1: Ellipticity parameter 1 :type e1: ``float`` :param e2: Ellipticity parameter 2 :type e2: ``float`` :param center_x: x coordinate of centroid :type center_x: ``float`` :param center_y: y coordianate of centroid :type center_y: ``float`` :return: Hessian :math:`\partial^2f/\partial x^2`, :math:`\partial^2 f/\partial y^2`, :math:`\partial^2/\partial x\partial y` for elliptical Gaussian convergence. :rtype: tuple ``(float, float, float)`` , or ``(numpy.array, numpy.array, numpy.array)`` with each ``numpy`` array's shape equal to ``x.shape`` """ f_xx = np.zeros_like(x, dtype=float) f_yy = np.zeros_like(x, dtype=float) f_xy = np.zeros_like(x, dtype=float) for i in range(len(amp)): f_xx_i, f_yy_i, f_xy_i = self.gaussian_ellipse_kappa.hessian( x, y, amp=amp[i], sigma=sigma[i], e1=e1, e2=e2, center_x=center_x, center_y=center_y) f_xx += f_xx_i f_yy += f_yy_i f_xy += f_xy_i return f_xx, f_yy, f_xy def density_2d(self, x, y, amp, sigma, e1, e2, center_x=0, center_y=0): """ Compute the density of a set of concentric elliptical Gaussian convergence profiles :math:`\sum A/(2\pi \sigma^2) \exp(-( x^2+y^2/q^2)/2\sigma^2)`. :param x: x coordinate :type x: ``float`` or ``numpy.array`` :param y: y coordinate :type y: ``float`` or ``numpy.array`` :param amp: Amplitude of Gaussian, convention: :math:`A/(2 \pi\sigma^2) \exp(-(x^2+y^2/q^2)/2\sigma^2)` :type amp: ``numpy.array`` with ``dtype=float`` :param sigma: Standard deviation of Gaussian :type sigma: ``numpy.array`` with ``dtype=float`` :param e1: Ellipticity parameter 1 :type e1: ``float`` :param e2: Ellipticity parameter 2 :type e2: ``float`` :param center_x: x coordinate of centroid :type center_x: ``float`` :param center_y: y coordianate of centroid :type center_y: ``float`` :return: Density :math:`\kappa` for elliptical Gaussian convergence :rtype: ``float``, or ``numpy.array`` with shape equal to ``x.shape`` """ density_2d = np.zeros_like(x, dtype=float) for i in range(len(amp)): density_2d += self.gaussian_ellipse_kappa.density_2d( x, y, amp=amp[i], sigma=sigma[i], e1=e1, e2=e2, center_x=center_x, center_y=center_y) return density_2d
class TestGaussianEllipseKappa(object): """ This class tests the methods for elliptical Gaussian convergence. """ def setup(self): """ :return: :rtype: """ self.gaussian_kappa = GaussianKappa() self.gaussian_kappa_ellipse = GaussianEllipseKappa() def test_function(self): """ Test the `function()` method at the spherical limit. :return: :rtype: """ # almost spherical case x = 1. y = 1. e1, e2 = 5e-5, 0. sigma = 1. amp = 2. f_ = self.gaussian_kappa_ellipse.function(x, y, amp, sigma, e1, e2) r2 = x*x + y*y f_sphere = amp/(2.*np.pi*sigma**2) * sigma**2 * (np.euler_gamma - expi(-r2/2./sigma**2) + np.log(r2/2./sigma**2)) npt.assert_almost_equal(f_, f_sphere, decimal=4) # spherical case e1, e2 = 0., 0. f_ = self.gaussian_kappa_ellipse.function(x, y, amp, sigma, e1, e2) npt.assert_almost_equal(f_, f_sphere, decimal=4) def test_derivatives(self): """ Test the `derivatives()` method at the spherical limit. :return: :rtype: """ # almost spherical case x = 1. y = 1. e1, e2 = 5e-5, 0. sigma = 1. amp = 2. f_x, f_y = self.gaussian_kappa_ellipse.derivatives(x, y, amp, sigma, e1, e2) f_x_sphere, f_y_sphere = self.gaussian_kappa.derivatives(x, y, amp=amp, sigma=sigma) npt.assert_almost_equal(f_x, f_x_sphere, decimal=4) npt.assert_almost_equal(f_y, f_y_sphere, decimal=4) # spherical case e1, e2 = 0., 0. f_x, f_y = self.gaussian_kappa_ellipse.derivatives(x, y, amp, sigma, e1, e2) npt.assert_almost_equal(f_x, f_x_sphere, decimal=4) npt.assert_almost_equal(f_y, f_y_sphere, decimal=4) def test_hessian(self): """ Test the `hessian()` method at the spherical limit. :return: :rtype: """ # almost spherical case x = 1. y = 1. e1, e2 = 5e-5, 0. sigma = 1. amp = 2. f_xx, f_yy, f_xy = self.gaussian_kappa_ellipse.hessian(x, y, amp, sigma, e1, e2) f_xx_sphere, f_yy_sphere, f_xy_sphere = self.gaussian_kappa.hessian(x, y, amp=amp, sigma=sigma) npt.assert_almost_equal(f_xx, f_xx_sphere, decimal=4) npt.assert_almost_equal(f_yy, f_yy_sphere, decimal=4) npt.assert_almost_equal(f_xy, f_xy_sphere, decimal=4) # spherical case e1, e2 = 0., 0. f_xx, f_yy, f_xy = self.gaussian_kappa_ellipse.hessian(x, y, amp, sigma, e1, e2) npt.assert_almost_equal(f_xx, f_xx_sphere, decimal=4) npt.assert_almost_equal(f_yy, f_yy_sphere, decimal=4) npt.assert_almost_equal(f_xy, f_xy_sphere, decimal=4) def test_density_2d(self): """ Test the `density_2d()` method at the spherical limit. :return: :rtype: """ # almost spherical case x = 1. y = 1. e1, e2 = 5e-5, 0. sigma = 1. amp = 2. f_ = self.gaussian_kappa_ellipse.density_2d(x, y, amp, sigma, e1, e2) f_sphere = amp / (2.*np.pi*sigma**2) * np.exp(-(x*x+y*y)/2./sigma**2) npt.assert_almost_equal(f_, f_sphere, decimal=4) def test_w_f_approx(self): """ Test the `w_f_approx()` method with values computed using `scipy.special.wofz()`. :return: :rtype: """ x = np.logspace(-3., 3., 100) y = np.logspace(-3., 3., 100) X, Y = np.meshgrid(x, y) w_f_app = self.gaussian_kappa_ellipse.w_f_approx(X+1j*Y) w_f_scipy = wofz(X+1j*Y) npt.assert_allclose(w_f_app.real, w_f_scipy.real, rtol=4e-5, atol=0) npt.assert_allclose(w_f_app.imag, w_f_scipy.imag, rtol=4e-5, atol=0) # check `derivatives()` method with and without `scipy.special.wofz()` x = 1. y = 1. e1, e2 = 5e-5, 0 sigma = 1. amp = 2. # with `scipy.special.wofz()` gauss_scipy = GaussianEllipseKappa(use_scipy_wofz=True) f_x_sp, f_y_sp = gauss_scipy.derivatives(x, y, amp, sigma, e1, e2) # with `GaussEllipseKappa.w_f_approx()` gauss_approx = GaussianEllipseKappa(use_scipy_wofz=False) f_x_ap, f_y_ap = gauss_approx.derivatives(x, y, amp, sigma, e1, e2) npt.assert_almost_equal(f_x_sp, f_x_ap, decimal=4) npt.assert_almost_equal(f_y_sp, f_y_ap, decimal=4)