Source code for autogalaxy.operate.deflections

from functools import wraps
import logging
import numpy as np
from typing import List, Tuple, Union

from autoconf import conf

import autoarray as aa

from autogalaxy.util.shear_field import ShearYX2D
from autogalaxy.util.shear_field import ShearYX2DIrregular

logger = logging.getLogger(__name__)


def grid_scaled_2d_for_marching_squares_from(
    grid_pixels_2d: aa.Grid2D,
    shape_native: Tuple[int, int],
    mask: aa.Mask2D,
) -> aa.Grid2DIrregular:
    pixel_scales = mask.pixel_scales
    origin = mask.origin

    grid_scaled_1d = aa.util.geometry.grid_scaled_2d_slim_from(
        grid_pixels_2d_slim=grid_pixels_2d,
        shape_native=shape_native,
        pixel_scales=pixel_scales,
        origin=origin,
    )

    grid_scaled_1d[:, 0] -= pixel_scales[0] / 2.0
    grid_scaled_1d[:, 1] += pixel_scales[1] / 2.0

    return aa.Grid2DIrregular(values=grid_scaled_1d)


def precompute_jacobian(func):
    @wraps(func)
    def wrapper(lensing_obj, grid, jacobian=None):
        if jacobian is None:
            jacobian = lensing_obj.jacobian_from(grid=grid)

        return func(lensing_obj, grid, jacobian)

    return wrapper


def evaluation_grid(func):
    @wraps(func)
    def wrapper(
        lensing_obj, grid, pixel_scale: Union[Tuple[float, float], float] = 0.05
    ):
        if hasattr(grid, "is_evaluation_grid"):
            if grid.is_evaluation_grid:
                return func(lensing_obj, grid, pixel_scale)

        pixel_scale_ratio = grid.pixel_scale / pixel_scale

        zoom_shape_native = grid.mask.zoom_shape_native
        shape_native = (
            int(pixel_scale_ratio * zoom_shape_native[0]),
            int(pixel_scale_ratio * zoom_shape_native[1]),
        )

        max_evaluation_grid_size = conf.instance["general"]["grid"][
            "max_evaluation_grid_size"
        ]

        # This is a hack to prevent the evaluation gird going beyond 1000 x 1000 pixels, which slows the code
        # down a lot. Need a better moe robust way to set this up for any general lens.

        if shape_native[0] > max_evaluation_grid_size:
            pixel_scale = pixel_scale_ratio / (
                shape_native[0] / float(max_evaluation_grid_size)
            )
            shape_native = (max_evaluation_grid_size, max_evaluation_grid_size)

        grid = aa.Grid2D.uniform(
            shape_native=shape_native,
            pixel_scales=(pixel_scale, pixel_scale),
            origin=grid.mask.zoom_offset_scaled,
        )

        grid.is_evaluation_grid = True

        return func(lensing_obj, grid, pixel_scale)

    return wrapper


[docs] class OperateDeflections: """ Packages methods which manipulate the 2D deflection angle map returned from the `deflections_yx_2d_from` function of a mass object (e.g. a `MassProfile`, `Galaxy`). The majority of methods are those which from the 2D deflection angle map compute lensing quantities like a 2D shear field, magnification map or the Einstein Radius. The methods in `CalcLens` are passed to the mass object to provide a concise API. Parameters ---------- deflections_yx_2d_from The function which returns the mass object's 2D deflection angles. """ def deflections_yx_2d_from(self, grid: aa.type.Grid2DLike, **kwargs): raise NotImplementedError def __eq__(self, other): return self.__dict__ == other.__dict__ and self.__class__ is other.__class__
[docs] @precompute_jacobian def tangential_eigen_value_from(self, grid, jacobian=None) -> aa.Array2D: """ Returns the tangential eigen values of lensing jacobian, which are given by the expression: `tangential_eigen_value = 1 - convergence - shear` Parameters ---------- grid The 2D grid of (y,x) arc-second coordinates the deflection angles and tangential eigen values are computed on. jacobian A precomputed lensing jacobian, which is passed throughout the `CalcLens` functions for efficiency. """ convergence = self.convergence_2d_via_jacobian_from( grid=grid, jacobian=jacobian ) shear_yx = self.shear_yx_2d_via_jacobian_from(grid=grid, jacobian=jacobian) return aa.Array2D(values=1 - convergence - shear_yx.magnitudes, mask=grid.mask)
[docs] @precompute_jacobian def radial_eigen_value_from(self, grid, jacobian=None) -> aa.Array2D: """ Returns the radial eigen values of lensing jacobian, which are given by the expression: radial_eigen_value = 1 - convergence + shear Parameters ---------- grid The 2D grid of (y,x) arc-second coordinates the deflection angles and radial eigen values are computed on. jacobian A precomputed lensing jacobian, which is passed throughout the `CalcLens` functions for efficiency. """ convergence = self.convergence_2d_via_jacobian_from( grid=grid, jacobian=jacobian ) shear = self.shear_yx_2d_via_jacobian_from(grid=grid, jacobian=jacobian) return aa.Array2D(values=1 - convergence + shear.magnitudes, mask=grid.mask)
[docs] def magnification_2d_from(self, grid) -> aa.Array2D: """ Returns the 2D magnification map of lensing object, which is computed as the inverse of the determinant of the jacobian. Parameters ---------- grid The 2D grid of (y,x) arc-second coordinates the deflection angles and magnification map are computed on. """ jacobian = self.jacobian_from(grid=grid) det_jacobian = jacobian[0][0] * jacobian[1][1] - jacobian[0][1] * jacobian[1][0] return aa.Array2D(values=1 / det_jacobian, mask=grid.mask)
[docs] def hessian_from(self, grid, buffer: float = 0.01, deflections_func=None) -> Tuple: """ Returns the Hessian of the lensing object, where the Hessian is the second partial derivatives of the potential (see equation 55 https://inspirehep.net/literature/419263): `hessian_{i,j} = d^2 / dtheta_i dtheta_j` The Hessian is computed by evaluating the 2D deflection angles around every (y,x) coordinate on the input 2D grid map in four directions (positive y, negative y, positive x, negative x), exploiting how the deflection angles are the derivative of the potential. By using evaluating the deflection angles around each grid coordinate, the Hessian can therefore be computed using uniform or irregular 2D grids of (y,x). This can be slower, because x4 more deflection angle calculations are required, however it is more flexible in and therefore used throughout **PyAutoLens** by default. The Hessian is returned as a 4 entry tuple, which reflect its structure as a 2x2 matrix. Parameters ---------- grid The 2D grid of (y,x) arc-second coordinates the deflection angles and Hessian are computed on. buffer The spacing in the y and x directions around each grid coordinate where deflection angles are computed and used to estimate the derivative. """ if deflections_func is None: deflections_func = self.deflections_yx_2d_from grid_shift_y_up = aa.Grid2DIrregular(values=np.zeros(grid.shape)) grid_shift_y_up[:, 0] = grid[:, 0] + buffer grid_shift_y_up[:, 1] = grid[:, 1] grid_shift_y_down = aa.Grid2DIrregular(values=np.zeros(grid.shape)) grid_shift_y_down[:, 0] = grid[:, 0] - buffer grid_shift_y_down[:, 1] = grid[:, 1] grid_shift_x_left = aa.Grid2DIrregular(values=np.zeros(grid.shape)) grid_shift_x_left[:, 0] = grid[:, 0] grid_shift_x_left[:, 1] = grid[:, 1] - buffer grid_shift_x_right = aa.Grid2DIrregular(values=np.zeros(grid.shape)) grid_shift_x_right[:, 0] = grid[:, 0] grid_shift_x_right[:, 1] = grid[:, 1] + buffer deflections_up = deflections_func(grid=grid_shift_y_up) deflections_down = deflections_func(grid=grid_shift_y_down) deflections_left = deflections_func(grid=grid_shift_x_left) deflections_right = deflections_func(grid=grid_shift_x_right) hessian_yy = 0.5 * (deflections_up[:, 0] - deflections_down[:, 0]) / buffer hessian_xy = 0.5 * (deflections_up[:, 1] - deflections_down[:, 1]) / buffer hessian_yx = 0.5 * (deflections_right[:, 0] - deflections_left[:, 0]) / buffer hessian_xx = 0.5 * (deflections_right[:, 1] - deflections_left[:, 1]) / buffer return hessian_yy, hessian_xy, hessian_yx, hessian_xx
[docs] def convergence_2d_via_hessian_from( self, grid, buffer: float = 0.01 ) -> aa.ArrayIrregular: """ Returns the convergence of the lensing object, which is computed from the 2D deflection angle map via the Hessian using the expression (see equation 56 https://inspirehep.net/literature/419263): `convergence = 0.5 * (hessian_{0,0} + hessian_{1,1}) = 0.5 * (hessian_xx + hessian_yy)` By going via the Hessian, the convergence can be calculated at any (y,x) coordinate therefore using either a 2D uniform or irregular grid. This calculation of the convergence is independent of analytic calculations defined within `MassProfile` objects and can therefore be used as a cross-check. Parameters ---------- grid The 2D grid of (y,x) arc-second coordinates the deflection angles and Hessian are computed on. buffer The spacing in the y and x directions around each grid coordinate where deflection angles are computed and used to estimate the derivative. """ hessian_yy, hessian_xy, hessian_yx, hessian_xx = self.hessian_from( grid=grid, buffer=buffer ) return aa.ArrayIrregular(values=0.5 * (hessian_yy + hessian_xx))
[docs] def shear_yx_2d_via_hessian_from( self, grid, buffer: float = 0.01 ) -> ShearYX2DIrregular: """ Returns the 2D (y,x) shear vectors of the lensing object, which are computed from the 2D deflection angle map via the Hessian using the expressions (see equation 57 https://inspirehep.net/literature/419263): `shear_y = hessian_{1,0} = hessian_{0,1} = hessian_yx = hessian_xy` `shear_x = 0.5 * (hessian_{0,0} - hessian_{1,1}) = 0.5 * (hessian_xx - hessian_yy)` By going via the Hessian, the shear vectors can be calculated at any (y,x) coordinate, therefore using either a 2D uniform or irregular grid. This calculation of the shear vectors is independent of analytic calculations defined within `MassProfile` objects and can therefore be used as a cross-check. The result is returned as a `ShearYX2D` dats structure, which has shape [total_shear_vectors, 2], where entries for [:,0] are the gamma_2 values and entries for [:,1] are the gamma_1 values. Note therefore that this convention means the FIRST entries in the array are the gamma_2 values and the SECOND entries are the gamma_1 values. Parameters ---------- grids The 2D grid of (y,x) arc-second coordinates the deflection angles and Hessian are computed on. buffer The spacing in the y and x directions around each grid coordinate where deflection angles are computed and used to estimate the derivative. """ hessian_yy, hessian_xy, hessian_yx, hessian_xx = self.hessian_from( grid=grid, buffer=buffer ) gamma_1 = 0.5 * (hessian_xx - hessian_yy) gamma_2 = hessian_xy shear_yx_2d = np.zeros(shape=(grid.shape_slim, 2)) shear_yx_2d[:, 0] = gamma_2 shear_yx_2d[:, 1] = gamma_1 return ShearYX2DIrregular(values=shear_yx_2d, grid=grid)
[docs] def magnification_2d_via_hessian_from( self, grid, buffer: float = 0.01, deflections_func=None ) -> aa.ArrayIrregular: """ Returns the 2D magnification map of lensing object, which is computed from the 2D deflection angle map via the Hessian using the expressions (see equation 60 https://inspirehep.net/literature/419263): `magnification = 1.0 / det(Jacobian) = 1.0 / abs((1.0 - convergence)**2.0 - shear**2.0)` `magnification = (1.0 - hessian_{0,0}) * (1.0 - hessian_{1, 1)) - hessian_{0,1}*hessian_{1,0}` `magnification = (1.0 - hessian_xx) * (1.0 - hessian_yy)) - hessian_xy*hessian_yx` By going via the Hessian, the magnification can be calculated at any (y,x) coordinate, therefore using either a 2D uniform or irregular grid. This calculation of the magnification is independent of calculations using the Jacobian and can therefore be used as a cross-check. Parameters ---------- grid The 2D grid of (y,x) arc-second coordinates the deflection angles and magnification map are computed on. """ hessian_yy, hessian_xy, hessian_yx, hessian_xx = self.hessian_from( grid=grid, buffer=buffer, deflections_func=deflections_func ) det_A = (1 - hessian_xx) * (1 - hessian_yy) - hessian_xy * hessian_yx return aa.ArrayIrregular(values=1.0 / det_A)
def contour_list_from(self, grid, contour_array): grid_contour = aa.Grid2DContour( grid=grid, pixel_scales=grid.pixel_scales, shape_native=grid.shape_native, contour_array=contour_array.native, ) return grid_contour.contour_list
[docs] @evaluation_grid def tangential_critical_curve_list_from( self, grid, pixel_scale: Union[Tuple[float, float], float] = 0.05 ) -> List[aa.Grid2DIrregular]: """ Returns all tangential critical curves of the lensing system, which are computed as follows: 1) Compute the tangential eigen values for every coordinate on the input grid via the Jacobian. 2) Find contours of all values in the tangential eigen values that are zero using a marching squares algorithm. Due to the use of a marching squares algorithm that requires the zero values of the tangential eigen values to be computed, critical curves can only be calculated using the Jacobian and a uniform 2D grid. Parameters ---------- grid The 2D grid of (y,x) arc-second coordinates the deflection angles and tangential eigen values are computed on. pixel_scale If input, the `evaluation_grid` decorator creates the 2D grid at this resolution, therefore enabling the critical curve to be computed more accurately using a higher resolution grid. """ tangential_eigen_values = self.tangential_eigen_value_from(grid=grid) return self.contour_list_from(grid=grid, contour_array=tangential_eigen_values)
[docs] @evaluation_grid def radial_critical_curve_list_from( self, grid, pixel_scale: Union[Tuple[float, float], float] = 0.05 ) -> List[aa.Grid2DIrregular]: """ Returns all radial critical curves of the lensing system, which are computed as follows: 1) Compute the radial eigen values for every coordinate on the input grid via the Jacobian. 2) Find contours of all values in the radial eigen values that are zero using a marching squares algorithm. Due to the use of a marching squares algorithm that requires the zero values of the radial eigen values to be computed, this critical curves can only be calculated using the Jacobian and a uniform 2D grid. Parameters ---------- grid The 2D grid of (y,x) arc-second coordinates the deflection angles and radial eigen values are computed on. pixel_scale If input, the `evaluation_grid` decorator creates the 2D grid at this resolution, therefore enabling the critical curve to be computed more accurately using a higher resolution grid. """ radial_eigen_values = self.radial_eigen_value_from(grid=grid) return self.contour_list_from(grid=grid, contour_array=radial_eigen_values)
[docs] @evaluation_grid def tangential_caustic_list_from( self, grid, pixel_scale: Union[Tuple[float, float], float] = 0.05 ) -> List[aa.Grid2DIrregular]: """ Returns all tangential caustics of the lensing system, which are computed as follows: 1) Compute the tangential eigen values for every coordinate on the input grid via the Jacobian. 2) Find contours of all values in the tangential eigen values that are zero using a marching squares algorithm. 3) Compute the lensing system's deflection angles at the (y,x) coordinates of the tangential critical curve contours and ray-trace it to the source-plane, therefore forming the tangential caustics. Due to the use of a marching squares algorithm that requires the zero values of the tangential eigen values to be computed, caustics can only be calculated using the Jacobian and a uniform 2D grid. Parameters ---------- grid The 2D grid of (y,x) arc-second coordinates the deflection angles and tangential eigen values are computed on. pixel_scale If input, the `evaluation_grid` decorator creates the 2D grid at this resolution, therefore enabling the caustic to be computed more accurately using a higher resolution grid. """ tangential_critical_curve_list = self.tangential_critical_curve_list_from( grid=grid, pixel_scale=pixel_scale ) tangential_caustic_list = [] for tangential_critical_curve in tangential_critical_curve_list: deflections_critical_curve = self.deflections_yx_2d_from( grid=tangential_critical_curve ) tangential_caustic_list.append( tangential_critical_curve - deflections_critical_curve ) return tangential_caustic_list
[docs] @evaluation_grid def radial_caustic_list_from( self, grid, pixel_scale: Union[Tuple[float, float], float] = 0.05 ) -> List[aa.Grid2DIrregular]: """ Returns all radial caustics of the lensing system, which are computed as follows: 1) Compute the radial eigen values for every coordinate on the input grid via the Jacobian. 2) Find contours of all values in the radial eigen values that are zero using a marching squares algorithm. 3) Compute the lensing system's deflection angles at the (y,x) coordinates of the radial critical curve contours and ray-trace it to the source-plane, therefore forming the radial caustics. Due to the use of a marching squares algorithm that requires the zero values of the radial eigen values to be computed, this caustics can only be calculated using the Jacobian and a uniform 2D grid. Parameters ---------- grid The 2D grid of (y,x) arc-second coordinates the deflection angles and radial eigen values are computed on. pixel_scale If input, the `evaluation_grid` decorator creates the 2D grid at this resolution, therefore enabling the caustic to be computed more accurately using a higher resolution grid. """ radial_critical_curve_list = self.radial_critical_curve_list_from( grid=grid, pixel_scale=pixel_scale ) radial_caustic_list = [] for radial_critical_curve in radial_critical_curve_list: deflections_critical_curve = self.deflections_yx_2d_from( grid=radial_critical_curve ) radial_caustic_list.append( radial_critical_curve - deflections_critical_curve ) return radial_caustic_list
[docs] @evaluation_grid def radial_critical_curve_area_list_from( self, grid, pixel_scale: Union[Tuple[float, float], float] ) -> List[float]: """ Returns the surface area within each radial critical curve as a list, the calculation of which is described in the function `radial_critical_curve_list_from()`. The area is computed via a line integral. Due to the use of a marching squares algorithm to estimate the critical curve, this function can only use the Jacobian and a uniform 2D grid. Parameters ---------- grid The 2D grid of (y,x) arc-second coordinates the deflection angles used to calculate the radial critical curve are computed on. pixel_scale If input, the `evaluation_grid` decorator creates the 2D grid at this resolution, therefore enabling the caustic to be computed more accurately using a higher resolution grid. """ radial_critical_curve_list = self.radial_critical_curve_list_from( grid=grid, pixel_scale=pixel_scale ) return self.area_within_curve_list_from(curve_list=radial_critical_curve_list)
[docs] @evaluation_grid def tangential_critical_curve_area_list_from( self, grid, pixel_scale: Union[Tuple[float, float], float] = 0.05 ) -> List[float]: """ Returns the surface area within each tangential critical curve as a list, the calculation of which is described in the function `tangential_critical_curve_list_from()`. The area is computed via a line integral. Due to the use of a marching squares algorithm to estimate the critical curve, this function can only use the Jacobian and a uniform 2D grid. Parameters ---------- grid The 2D grid of (y,x) arc-second coordinates the deflection angles used to calculate the tangential critical curve are computed on. pixel_scale If input, the `evaluation_grid` decorator creates the 2D grid at this resolution, therefore enabling the caustic to be computed more accurately using a higher resolution grid. """ tangential_critical_curve_list = self.tangential_critical_curve_list_from( grid=grid, pixel_scale=pixel_scale ) return self.area_within_curve_list_from( curve_list=tangential_critical_curve_list )
def area_within_curve_list_from( self, curve_list: List[aa.Grid2DIrregular] ) -> List[float]: area_within_each_curve_list = [] for curve in curve_list: x, y = curve[:, 0], curve[:, 1] area = np.abs(0.5 * np.sum(y[:-1] * np.diff(x) - x[:-1] * np.diff(y))) area_within_each_curve_list.append(area) return area_within_each_curve_list
[docs] @evaluation_grid def einstein_radius_list_from( self, grid, pixel_scale: Union[Tuple[float, float], float] = 0.05 ): """ Returns a list of the Einstein radii corresponding to the area within each tangential critical curve. Each Einstein radius is defined as the radius of the circle which contains the same area as the area within each tangential critical curve. This definition is sometimes referred to as the "effective Einstein radius" in the literature and is commonly adopted in studies, for example the SLACS series of papers. The calculation of the tangential critical curves and their areas is described in the functions `tangential_critical_curve_list_from()` and `tangential_critical_curve_area_list_from()`. Due to the use of a marching squares algorithm to estimate the critical curve, this function can only use the Jacobian and a uniform 2D grid. Parameters ---------- grid The 2D grid of (y,x) arc-second coordinates the deflection angles used to calculate the tangential critical curve are computed on. pixel_scale If input, the `evaluation_grid` decorator creates the 2D grid at this resolution, therefore enabling the caustic to be computed more accurately using a higher resolution grid. """ try: area_list = self.tangential_critical_curve_area_list_from( grid=grid, pixel_scale=pixel_scale ) return [np.sqrt(area / np.pi) for area in area_list] except TypeError: raise TypeError("The grid input was unable to estimate the Einstein Radius")
[docs] @evaluation_grid def einstein_radius_from( self, grid, pixel_scale: Union[Tuple[float, float], float] = 0.05 ): """ Returns the Einstein radius corresponding to the area within the tangential critical curve. The Einstein radius is defined as the radius of the circle which contains the same area as the area within the tangential critical curve. This definition is sometimes referred to as the "effective Einstein radius" in the literature and is commonly adopted in studies, for example the SLACS series of papers. If there are multiple tangential critical curves (e.g. because the mass distribution is complex) this function raises an error, and the function `einstein_radius_list_from()` should be used instead. The calculation of the tangential critical curves and their areas is described in the functions `tangential_critical_curve_list_from()` and `tangential_critical_curve_area_list_from()`. Due to the use of a marching squares algorithm to estimate the critical curve, this function can only use the Jacobian and a uniform 2D grid. Parameters ---------- grid The 2D grid of (y,x) arc-second coordinates the deflection angles used to calculate the tangential critical curve are computed on. pixel_scale If input, the `evaluation_grid` decorator creates the 2D grid at this resolution, therefore enabling the caustic to be computed more accurately using a higher resolution grid. """ einstein_radii_list = self.einstein_radius_list_from(grid=grid) if len(einstein_radii_list) > 1: logger.info( """ There are multiple tangential critical curves, and the computed Einstein radius is the sum of all of them. Check the `einstein_radius_list_from` function for the individual Einstein. """ ) return sum(einstein_radii_list)
[docs] @evaluation_grid def einstein_mass_angular_list_from( self, grid, pixel_scale: Union[Tuple[float, float], float] = 0.05 ) -> List[float]: """ Returns a list of the angular Einstein massses corresponding to the area within each tangential critical curve. The angular Einstein mass is defined as: `einstein_mass = pi * einstein_radius ** 2.0` where the Einstein radius is the radius of the circle which contains the same area as the area within the tangential critical curve. The Einstein mass is returned in units of arcsecond**2.0 and requires division by the lensing critical surface density \sigma_cr to be converted to physical units like solar masses (see `autogalaxy.util.cosmology_util`). This definition of Eisntein radius (and therefore mass) is sometimes referred to as the "effective Einstein radius" in the literature and is commonly adopted in studies, for example the SLACS series of papers. The calculation of the einstein radius is described in the function `einstein_radius_from()`. Due to the use of a marching squares algorithm to estimate the critical curve, this function can only use the Jacobian and a uniform 2D grid. Parameters ---------- grid The 2D grid of (y,x) arc-second coordinates the deflection angles used to calculate the tangential critical curve are computed on. pixel_scale If input, the `evaluation_grid` decorator creates the 2D grid at this resolution, therefore enabling the caustic to be computed more accurately using a higher resolution grid. """ einstein_radius_list = self.einstein_radius_list_from( grid=grid, pixel_scale=pixel_scale ) return [ np.pi * einstein_radius**2 for einstein_radius in einstein_radius_list ]
[docs] @evaluation_grid def einstein_mass_angular_from( self, grid, pixel_scale: Union[Tuple[float, float], float] = 0.05 ) -> float: """ Returns the Einstein radius corresponding to the area within the tangential critical curve. The angular Einstein mass is defined as: `einstein_mass = pi * einstein_radius ** 2.0` where the Einstein radius is the radius of the circle which contains the same area as the area within the tangential critical curve. The Einstein mass is returned in units of arcsecond**2.0 and requires division by the lensing critical surface density \sigma_cr to be converted to physical units like solar masses (see `autogalaxy.util.cosmology_util`). This definition of Eisntein radius (and therefore mass) is sometimes referred to as the "effective Einstein radius" in the literature and is commonly adopted in studies, for example the SLACS series of papers. The calculation of the einstein radius is described in the function `einstein_radius_from()`. Due to the use of a marching squares algorithm to estimate the critical curve, this function can only use the Jacobian and a uniform 2D grid. Parameters ---------- grid The 2D grid of (y,x) arc-second coordinates the deflection angles used to calculate the tangential critical curve are computed on. pixel_scale If input, the `evaluation_grid` decorator creates the 2D grid at this resolution, therefore enabling the caustic to be computed more accurately using a higher resolution grid. """ einstein_mass_angular_list = self.einstein_mass_angular_list_from( grid=grid, pixel_scale=pixel_scale ) if len(einstein_mass_angular_list) > 1: logger.info( """ There are multiple tangential critical curves, and the computed Einstein mass is the sum of all of them. Check the `einstein_mass_list_from` function for the individual Einstein. """ ) return einstein_mass_angular_list[0]
[docs] def jacobian_from(self, grid): """ Returns the Jacobian of the lensing object, which is computed by taking the gradient of the 2D deflection angle map in four direction (positive y, negative y, positive x, negative x). By using the `np.gradient` method the Jacobian can therefore only be computed using uniform 2D grids of (y,x) coordinates, and does not support irregular grids. For this reason, calculations by default use the Hessian, which is slower to compute because more deflection angle calculations are necessary but more flexible in general. The Jacobian is returned as a list of lists, which reflect its structure as a 2x2 matrix. Parameters ---------- grid The 2D grid of (y,x) arc-second coordinates the deflection angles and Jacobian are computed on. """ deflections = self.deflections_yx_2d_from(grid=grid) # TODO : Can probably make this work on irregular grid? Is there any point? a11 = aa.Array2D( values=1.0 - np.gradient(deflections.native[:, :, 1], grid.native[0, :, 1], axis=1), mask=grid.mask, ) a12 = aa.Array2D( values=-1.0 * np.gradient(deflections.native[:, :, 1], grid.native[:, 0, 0], axis=0), mask=grid.mask, ) a21 = aa.Array2D( values=-1.0 * np.gradient(deflections.native[:, :, 0], grid.native[0, :, 1], axis=1), mask=grid.mask, ) a22 = aa.Array2D( values=1 - np.gradient(deflections.native[:, :, 0], grid.native[:, 0, 0], axis=0), mask=grid.mask, ) return [[a11, a12], [a21, a22]]
[docs] @precompute_jacobian def convergence_2d_via_jacobian_from(self, grid, jacobian=None) -> aa.Array2D: """ Returns the convergence of the lensing object, which is computed from the 2D deflection angle map via the Jacobian using the expression (see equation 58 https://inspirehep.net/literature/419263): `convergence = 1.0 - 0.5 * (jacobian_{0,0} + jacobian_{1,1}) = 0.5 * (jacobian_xx + jacobian_yy)` By going via the Jacobian, the convergence must be calculated using 2D uniform grid. This calculation of the convergence is independent of analytic calculations defined within `MassProfile` objects and the calculation via the Hessian. It can therefore be used as a cross-check. Parameters ---------- grid The 2D grid of (y,x) arc-second coordinates the deflection angles and Jacobian are computed on. jacobian A precomputed lensing jacobian, which is passed throughout the `CalcLens` functions for efficiency. """ convergence = 1 - 0.5 * (jacobian[0][0] + jacobian[1][1]) return aa.Array2D(values=convergence, mask=grid.mask)
[docs] @precompute_jacobian def shear_yx_2d_via_jacobian_from( self, grid, jacobian=None ) -> Union[ShearYX2D, ShearYX2DIrregular]: """ Returns the 2D (y,x) shear vectors of the lensing object, which are computed from the 2D deflection angle map via the Jacobian using the expression (see equation 58 https://inspirehep.net/literature/419263): `shear_y = -0.5 * (jacobian_{0,1} + jacobian_{1,0} = -0.5 * (jacobian_yx + jacobian_xy)` `shear_x = 0.5 * (jacobian_{1,1} + jacobian_{0,0} = 0.5 * (jacobian_yy + jacobian_xx)` By going via the Jacobian, the convergence must be calculated using 2D uniform grid. This calculation of the shear vectors is independent of analytic calculations defined within `MassProfile` objects and the calculation via the Hessian. It can therefore be used as a cross-check. Parameters ---------- grid The 2D grid of (y,x) arc-second coordinates the deflection angles and Jacobian are computed on. jacobian A precomputed lensing jacobian, which is passed throughout the `CalcLens` functions for efficiency. """ shear_yx_2d = np.zeros(shape=(grid.shape_slim, 2)) shear_yx_2d[:, 0] = -0.5 * (jacobian[0][1] + jacobian[1][0]) shear_yx_2d[:, 1] = 0.5 * (jacobian[1][1] - jacobian[0][0]) if isinstance(grid, aa.Grid2DIrregular): return ShearYX2DIrregular(values=shear_yx_2d, grid=grid) return ShearYX2D(values=shear_yx_2d, grid=grid, mask=grid.mask)