Source code for caustics.lenses.base

# mypy: disable-error-code="call-overload"
from abc import abstractmethod
from typing import Optional, Union, Annotated
import warnings

from caskade import Module, Param, forward

from ..backend_obj import backend, ArrayLike
from ..cosmology import Cosmology
from .utils import magnification
from . import func

__all__ = ("ThinLens", "ThickLens")

CosmologyType = Annotated[
    Cosmology,
    "Cosmology object that encapsulates cosmological parameters and distances",
]
NameType = Annotated[Optional[str], "Name of the lens model"]
ZType = Annotated[
    Optional[Union[ArrayLike, float]],
    "The redshift of an object in the lens system",
    True,
]


class Lens(Module):
    """
    Base class for all lenses
    """

    def __init__(
        self, cosmology: CosmologyType, name: NameType = None, z_s: ZType = None
    ):
        """
        Initializes a new instance of the Lens class.

        Parameters
        ----------
        name: string
            The name of the lens model.

        cosmology: Cosmology
            An instance of a Cosmology class that describes the cosmological
            parameters of the model.

        z_s: ZType
            Redshift of the source. Needed for various lensing calculations so
            z_s is held by the lens object rather than the source object.
        """
        super().__init__(name)
        self.cosmology = cosmology
        self.z_s = Param("z_s", z_s, units="unitless", valid=(0, None))

    @forward
    def jacobian_lens_equation(
        self,
        x: ArrayLike,
        y: ArrayLike,
        method="autograd",
        pixelscale=None,
        **kwargs,
    ) -> tuple[tuple[ArrayLike, ArrayLike], tuple[ArrayLike, ArrayLike]]:
        """
        Return the jacobian of the lensing equation at specified points.
        This equates to a (2,2) matrix at each (x,y) point.

        method: autograd or fft
        """

        if method == "autograd":
            return self._jacobian_lens_equation_autograd(x, y, **kwargs)
        elif method == "finitediff":
            if pixelscale is None:
                raise ValueError(
                    "Finite differences lensing jacobian requires regular grid "
                    "and known pixelscale. "
                    "Please include the pixelscale argument"
                )
            return self._jacobian_lens_equation_finitediff(x, y, pixelscale, **kwargs)
        else:
            raise ValueError("method should be one of: autograd, finitediff")

    @forward
    def shear(
        self,
        x: ArrayLike,
        y: ArrayLike,
        method="autograd",
        pixelscale: Optional[ArrayLike] = None,
    ):
        """
        General shear calculation for a lens model using the jacobian of the
        lens equation. Individual lenses may implement more efficient methods.
        """
        A = self.jacobian_lens_equation(x, y, method=method, pixelscale=pixelscale)
        I = backend.eye(2, device=A.device, dtype=A.dtype).reshape(  # noqa E741
            *[1] * len(A.shape[:-2]), 2, 2
        )
        negPsi = (
            0.5
            * backend.unsqueeze(backend.unsqueeze(A[..., 0, 0] + A[..., 1, 1], -1), -1)
            * I
            - A
        )
        return 0.5 * (negPsi[..., 0, 0] - negPsi[..., 1, 1]), negPsi[..., 0, 1]

    @forward
    def magnification(
        self,
        x: ArrayLike,
        y: ArrayLike,
    ) -> ArrayLike:
        """
        Compute the gravitational magnification at the given coordinates.

        Parameters
        ----------
        x: ArrayLike
            ArrayLike of x coordinates in the lens plane.

            *Unit: arcsec*

        y: ArrayLike
            ArrayLike of y coordinates in the lens plane.

            *Unit: arcsec*

        Returns
        -------
        ArrayLike
            Gravitational magnification at the given coordinates.

            *Unit: unitless*

        """
        return magnification(self.raytrace, x, y)

    @forward
    def forward_raytrace(
        self,
        bx: ArrayLike,
        by: ArrayLike,
        epsilon: float = 1e-3,
        x0: Optional[ArrayLike] = None,
        y0: Optional[ArrayLike] = None,
        fov: float = 5.0,
        divisions: int = 100,
        max_depth: int = 25,
    ) -> tuple[ArrayLike, ArrayLike]:
        """
        Perform a forward ray-tracing operation which maps from the source plane
        to the image plane.

        Parameters
        ----------
        bx: ArrayLike
            ArrayLike of x coordinate in the source plane.

            *Unit: arcsec*

        by: ArrayLike
            ArrayLike of y coordinate in the source plane.

            *Unit: arcsec*

        epsilon: ArrayLike
            maximum distance between two images (arcsec) before they are
            considered the same image.

            *Unit: arcsec*

        fov: float
            the field of view in which the initial random samples are taken.

            *Unit: arcsec*

        divisions: int
            the number of divisions of the fov on each axis when constructing
            the grid to perform in the triangle search.

        Returns
        -------
        x_component: ArrayLike
            x-coordinate ArrayLike of the ray-traced light rays

            *Unit: arcsec*

        y_component: ArrayLike
            y-coordinate ArrayLike of the ray-traced light rays

            *Unit: arcsec*
        """
        if x0 is None:
            x0 = backend.zeros((), device=bx.device, dtype=bx.dtype)
        if y0 is None:
            y0 = backend.zeros((), device=by.device, dtype=by.dtype)

        return func.forward_raytrace(
            backend.stack((bx, by)),
            self.raytrace,
            x0,
            y0,
            fov,
            divisions,
            epsilon,
            max_depth,
        )


[docs] class ThickLens(Lens): """ Base class for modeling gravitational lenses that cannot be treated using the thin lens approximation. It is an abstract class and should be subclassed for different types of lens models. Attributes ---------- cosmology: Cosmology An instance of a Cosmology class that describes the cosmological parameters of the model. """
[docs] @forward def reduced_deflection_angle( self, x: ArrayLike, y: ArrayLike, **kwargs, ) -> tuple[ArrayLike, ArrayLike]: """ ThickLens objects do not have a reduced deflection angle since the distance D_ls is undefined Parameters ---------- x: ArrayLike ArrayLike of x coordinates in the lens plane. *Unit: arcsec* y: ArrayLike ArrayLike of y coordinates in the lens plane. *Unit: unitless* """ warnings.warn( "ThickLens objects do not have a reduced deflection angle " "since they have no unique lens redshift. " "The distance D_{ls} is undefined in the equation " "$\\alpha_{reduced} = \\frac{D_{ls}}{D_s}\\alpha_{physical}$." "See `effective_reduced_deflection_angle`. " "Now using effective_reduced_deflection_angle, " "please switch functions to remove this warning" ) return self.effective_reduced_deflection_angle(x, y, **kwargs)
[docs] @forward def effective_reduced_deflection_angle( self, x: ArrayLike, y: ArrayLike, **kwargs, ) -> tuple[ArrayLike, ArrayLike]: """ThickLens objects do not have a reduced deflection angle since the distance D_ls is undefined. Instead we define an effective reduced deflection angle by simply assuming the relation $\alpha = \theta - \beta$ holds, where $\alpha$ is the effective reduced deflection angle, $\theta$ are the observed angular coordinates, and $\beta$ are the angular coordinates to the source plane. Parameters ---------- x: ArrayLike ArrayLike of x coordinates in the lens plane. *Unit: arcsec* y: ArrayLike ArrayLike of y coordinates in the lens plane. *Unit: arcsec* """ bx, by = self.raytrace(x, y, **kwargs) return x - bx, y - by
[docs] @forward def physical_deflection_angle( self, x: ArrayLike, y: ArrayLike, *args, **kwargs, ) -> tuple[ArrayLike, ArrayLike]: """Physical deflection angles are computed with respect to a lensing plane. ThickLens objects have no unique definition of a lens plane and so cannot compute a physical_deflection_angle Parameters ---------- x: ArrayLike ArrayLike of x coordinates in the lens plane. *Unit: arcsec* y: ArrayLike ArrayLike of y coordinates in the lens plane. *Unit: arcsec* Returns ------- x_component: ArrayLike Deflection Angle in x direction. *Unit: arcsec* y_component: ArrayLike Deflection Angle in y direction. *Unit: arcsec* """ raise NotImplementedError( "Physical deflection angles are computed with respect to a lensing plane. " "ThickLens objects have no unique definition of a lens plane " "and so cannot compute a physical_deflection_angle" )
[docs] @abstractmethod @forward def raytrace( self, x: ArrayLike, y: ArrayLike, *args, **kwargs, ) -> tuple[ArrayLike, ArrayLike]: """Performs ray tracing by computing the angular position on the source plance associated with a given input observed angular coordinate x,y. Parameters ---------- x: ArrayLike ArrayLike of x coordinates in the lens plane. *Unit: arcsec* y: ArrayLike ArrayLike of y coordinates in the lens plane. *Unit: arcsec* Returns ------- x: ArrayLike x coordinate ArrayLike of the ray-traced light rays *Unit: arcsec* y: ArrayLike y coordinate ArrayLike of the ray-traced light rays *Unit: arcsec* """ ...
[docs] @abstractmethod @forward def surface_density( self, x: ArrayLike, y: ArrayLike, *args, **kwargs, ) -> ArrayLike: """ Computes the projected mass density at given coordinates. Parameters ---------- x: ArrayLike ArrayLike of x coordinates in the lens plane. *Unit: arcsec* y: ArrayLike ArrayLike of y coordinates in the lens plane. *Unit: arcsec* Returns ------- ArrayLike The projected mass density at the given coordinates in units of solar masses per square Mpc. *Unit: Msun/Mpc^2* """ ...
[docs] @abstractmethod @forward def time_delay( self, x: ArrayLike, y: ArrayLike, *args, **kwargs, ) -> ArrayLike: """ Computes the gravitational time delay at given coordinates. Parameters ---------- x: ArrayLike ArrayLike of x coordinates in the lens plane. *Unit: arcsec* y: ArrayLike ArrayLike of y coordinates in the lens plane. *Unit: arcsec* Returns ------- ArrayLike The gravitational time delay at the given coordinates. *Unit: seconds* """ ...
@forward def _jacobian_effective_deflection_angle_finitediff( self, x: ArrayLike, y: ArrayLike, pixelscale: ArrayLike, ) -> tuple[tuple[ArrayLike, ArrayLike], tuple[ArrayLike, ArrayLike]]: """ Return the jacobian of the effective reduced deflection angle vector field. This equates to a (2,2) matrix at each (x,y) point. """ # Compute deflection angles ax, ay = self.effective_reduced_deflection_angle(x, y) # Build Jacobian J = backend.zeros((*ax.shape, 2, 2), device=ax.device, dtype=ax.dtype) grad_ax = backend.gradient(ax, spacing=pixelscale) grad_ay = backend.gradient(ay, spacing=pixelscale) J = backend.fill_at_indices(J, (Ellipsis, 0, 1), grad_ax[0]) J = backend.fill_at_indices(J, (Ellipsis, 0, 0), grad_ax[1]) J = backend.fill_at_indices(J, (Ellipsis, 1, 1), grad_ay[0]) J = backend.fill_at_indices(J, (Ellipsis, 1, 0), grad_ay[1]) return J @forward def _jacobian_effective_deflection_angle_autograd( self, x: ArrayLike, y: ArrayLike, chunk_size: Optional[int] = None, ) -> tuple[tuple[ArrayLike, ArrayLike], tuple[ArrayLike, ArrayLike]]: """ Return the jacobian of the effective reduced deflection angle vector field. This equates to a (2,2) matrix at each (x,y) point. """ J = backend.vmap( backend.jacfwd( self.effective_reduced_deflection_angle, argnums=(0, 1), randomness="different", ), chunk_size=chunk_size, )(backend.flatten(x), backend.flatten(y)) J = backend.stack([backend.stack(Jrow, dim=-1) for Jrow in J], dim=-2) return J.reshape(*x.shape, 2, 2)
[docs] @forward def jacobian_effective_deflection_angle( self, x: ArrayLike, y: ArrayLike, method="autograd", pixelscale=None, **kwargs, ) -> tuple[tuple[ArrayLike, ArrayLike], tuple[ArrayLike, ArrayLike]]: """ Return the jacobian of the effective reduced deflection angle vector field. This equates to a (2,2) matrix at each (x,y) point. method: autograd or fft """ if method == "autograd": return self._jacobian_effective_deflection_angle_autograd(x, y, **kwargs) elif method == "finitediff": if pixelscale is None: raise ValueError( "Finite differences lensing jacobian requires " "regular grid and known pixelscale. " "Please include the pixelscale argument" ) return self._jacobian_effective_deflection_angle_finitediff( x, y, pixelscale, **kwargs ) else: raise ValueError("method should be one of: autograd, finitediff")
@forward def _jacobian_lens_equation_finitediff( self, x: ArrayLike, y: ArrayLike, pixelscale: ArrayLike, **kwargs, ) -> tuple[tuple[ArrayLike, ArrayLike], tuple[ArrayLike, ArrayLike]]: """ Return the jacobian of the lensing equation at specified points. This equates to a (2,2) matrix at each (x,y) point. """ # Build Jacobian J = self._jacobian_effective_deflection_angle_finitediff( x, y, pixelscale, **kwargs ) return backend.to(backend.eye(2), device=J.device) - J @forward def _jacobian_lens_equation_autograd( self, x: ArrayLike, y: ArrayLike, **kwargs, ) -> tuple[tuple[ArrayLike, ArrayLike], tuple[ArrayLike, ArrayLike]]: """ Return the jacobian of the lensing equation at specified points. This equates to a (2,2) matrix at each (x,y) point. """ # Build Jacobian J = self._jacobian_effective_deflection_angle_autograd(x, y, **kwargs) return backend.to(backend.eye(2), device=J.device) - backend.detach(J)
[docs] @forward def effective_convergence_div( self, x: ArrayLike, y: ArrayLike, **kwargs, ) -> ArrayLike: """ Using the divergence of the effective reduced delfection angle we can compute the divergence component of the effective convergence field. This field produces a single plane convergence field which reproduces as much of the deflection field as possible for a single plane. See: https://arxiv.org/pdf/2006.07383.pdf see also the `effective_convergence_curl` method. """ J = self.jacobian_effective_deflection_angle(x, y, **kwargs) return 0.5 * (J[..., 0, 0] + J[..., 1, 1])
[docs] @forward def effective_convergence_curl( self, x: ArrayLike, y: ArrayLike, **kwargs, ) -> ArrayLike: """ Use the curl of the effective reduced deflection angle vector field to compute an effective convergence which derives specifically from the curl of the deflection field. This field is purely a result of multiplane lensing and cannot occur in single plane lensing. See: https://arxiv.org/pdf/2006.07383.pdf """ J = self.jacobian_effective_deflection_angle(x, y, **kwargs) return 0.5 * (J[..., 1, 0] - J[..., 0, 1])
[docs] class ThinLens(Lens): """Base class for thin gravitational lenses. This class provides an interface for thin gravitational lenses, i.e., lenses that can be modeled using the thin lens approximation. The class provides methods to compute several lensing quantities such as the deflection angle, convergence, potential, surface mass density, and gravitational time delay. Attributes ---------- name: string Name of the lens model. cosmology: Cosmology Cosmology object that encapsulates cosmological parameters and distances. z_l: (Optional[ArrayLike], optional) Redshift of the lens. Defaults to None. *Unit: unitless* """ def __init__( self, cosmology: CosmologyType, z_l: ZType = None, z_s: ZType = None, name: NameType = None, ): super().__init__(cosmology=cosmology, name=name, z_s=z_s) self.z_l = Param("z_l", z_l, units="unitless", valid=(0, None))
[docs] @forward def reduced_deflection_angle( self, x: ArrayLike, y: ArrayLike, chunk_size: Optional[int] = None ) -> tuple[ArrayLike, ArrayLike]: """ Computes the reduced deflection angle of the lens at given coordinates [arcsec]. Parameters ---------- x: ArrayLike ArrayLike of x coordinates in the lens plane. *Unit: arcsec* y: ArrayLike ArrayLike of y coordinates in the lens plane. *Unit: arcsec* chunk_size: int Chunk size for the autograd computation. *Unit: number* Returns -------- x_component: ArrayLike Deflection Angle in the x-direction. *Unit: arcsec* y_component: ArrayLike Deflection Angle in the y-direction. *Unit: arcsec* """ ax, ay = backend.vmap( backend.grad(self.potential, (0, 1)), chunk_size=chunk_size )(backend.flatten(x), backend.flatten(y)) return ax.reshape(x.shape), ay.reshape(y.shape)
[docs] @forward def physical_deflection_angle( self, x: ArrayLike, y: ArrayLike, z_s: Annotated[ArrayLike, "Param"], z_l: Annotated[ArrayLike, "Param"], ) -> tuple[ArrayLike, ArrayLike]: """ Computes the physical deflection angle immediately after passing through this lens's plane. Parameters ---------- x: ArrayLike ArrayLike of x coordinates in the lens plane. *Unit: arcsec* y: ArrayLike ArrayLike of y coordinates in the lens plane. *Unit: arcsec* Returns ------- x_component: ArrayLike Deflection Angle in x-direction. *Unit: arcsec* y_component: ArrayLike Deflection Angle in y-direction. *Unit: arcsec* """ d_s = self.cosmology.angular_diameter_distance(z_s) d_ls = self.cosmology.angular_diameter_distance_z1z2(z_l, z_s) deflection_angle_x, deflection_angle_y = self.reduced_deflection_angle(x, y) return func.physical_from_reduced_deflection_angle( deflection_angle_x, deflection_angle_y, d_s, d_ls )
[docs] @abstractmethod @forward def convergence( self, x: ArrayLike, y: ArrayLike, chunk_size: Optional[int] = None, *args, **kwargs, ) -> ArrayLike: """ Computes the convergence of the lens at given coordinates. Parameters ---------- x: ArrayLike ArrayLike of x coordinates in the lens plane. *Unit: arcsec* y: ArrayLike ArrayLike of y coordinates in the lens plane. *Unit: arcsec* chunk_size: int Chunk size for the autograd computation. *Unit: number* Returns ------- ArrayLike Dimensionless convergence, normalized by the critical surface density at the lens plane *Unit: unitless* """ Psi_H = backend.vmap( backend.hessian(self.potential, (0, 1)), chunk_size=chunk_size, )(backend.flatten(x), backend.flatten(y)) Psi_H = backend.stack([backend.stack(Hrow, dim=-1) for Hrow in Psi_H], dim=-2) Psi_H = Psi_H.reshape(*x.shape, 2, 2) return 0.5 * (Psi_H[..., 0, 0] + Psi_H[..., 1, 1]).reshape(x.shape)
[docs] @abstractmethod @forward def potential( self, x: ArrayLike, y: ArrayLike, *args, **kwargs, ) -> ArrayLike: """ Computes the gravitational lensing potential at given coordinates. Parameters ---------- x: ArrayLike ArrayLike of x coordinates in the lens plane. *Unit: arcsec* y: ArrayLike ArrayLike of y coordinates in the lens plane. *Unit: arcsec* Returns ------- ArrayLike Gravitational lensing potential at the given coordinates in arcsec^2. *Unit: arsec^2* """ ...
[docs] @forward def surface_density( self, x: ArrayLike, y: ArrayLike, z_s: Annotated[ArrayLike, "Param"], z_l: Annotated[ArrayLike, "Param"], ) -> ArrayLike: """ Computes the surface mass density of the lens at given coordinates. Parameters ---------- x: ArrayLike ArrayLike of x coordinates in the lens plane. *Unit: arcsec* y: ArrayLike ArrayLike of y coordinates in the lens plane. *Unit: arcsec* Returns ------- ArrayLike Surface mass density at the given coordinates in solar masses per Mpc^2. *Unit: Msun/Mpc^2* """ critical_surface_density = self.cosmology.critical_surface_density(z_l, z_s) return self.convergence(x, y) * critical_surface_density # fmt: skip
[docs] @forward def raytrace( self, x: ArrayLike, y: ArrayLike, **kwargs, ) -> tuple[ArrayLike, ArrayLike]: """ Perform a ray-tracing operation by subtracting the deflection angles from the input coordinates. Parameters ---------- x: ArrayLike ArrayLike of x coordinates in the lens plane. *Unit: arcsec* y: ArrayLike ArrayLike of y coordinates in the lens plane. *Unit: arcsec* Returns ------- x_component: ArrayLike Deflection Angle in x direction. *Unit: arcsec* y_component: ArrayLike Deflection Angle in y direction. *Unit: arcsec* """ ax, ay = self.reduced_deflection_angle(x, y, **kwargs) return x - ax, y - ay
@forward def _arcsec2_to_days( self, z_s: Annotated[ArrayLike, "Param"], z_l: Annotated[ArrayLike, "Param"] ): """ This method is used by :func:`caustics.lenses.ThinLens.time_delay` to convert arcsec^2 to days in the context of gravitational time delays. """ d_l = self.cosmology.angular_diameter_distance(z_l) d_s = self.cosmology.angular_diameter_distance(z_s) d_ls = self.cosmology.angular_diameter_distance_z1z2(z_l, z_s) return func.time_delay_arcsec2_to_days(d_l, d_s, d_ls, z_l)
[docs] @forward def time_delay( self, x: ArrayLike, y: ArrayLike, shapiro_time_delay: bool = True, geometric_time_delay: bool = True, ) -> ArrayLike: """ Computes the gravitational time delay for light passing through the lens at given coordinates. This time delay is induced by the photons traveling through a gravitational potential well (Shapiro time delay) plus the effect of the increased path length that the photons must traverse (geometric time delay). The main equation involved here is the following: .. math:: \\Delta t = \\frac{1 + z_l}{c} \\frac{D_s}{D_l D_{ls}} \\left[ \\frac{1}{2}|\\vec{\\alpha}(\\vec{\\theta})|^2 - \\psi(\\vec{\\theta}) \\right] where :math:`\\vec{\\alpha}(\\vec{\\theta})` is the deflection angle, :math:`\\psi(\\vec{\\theta})` is the lensing potential, :math:`D_l` is the comoving distance to the lens, :math:`D_s` is the comoving distance to the source, and :math:`D_{ls}` is the comoving distance between the lens and the source. In the above equation, the first term is the geometric time delay and the second term is the gravitational time delay. Parameters ---------- x: ArrayLike ArrayLike of x coordinates in the lens plane. *Unit: arcsec* y: ArrayLike ArrayLike of y coordinates in the lens plane. *Unit: arcsec* shapiro_time_delay: bool Whether to include the Shapiro time delay component. geometric_time_delay: bool Whether to include the geometric time delay component. Returns ------- ArrayLike Time delay at the given coordinates. *Unit: days* References ---------- 1. Irwin I. Shapiro (1964). "Fourth Test of General Relativity". Physical Review Letters. 13 (26): 789-791 2. Refsdal, S. (1964). "On the possibility of determining Hubble's parameter and the masses of galaxies from the gravitational lens effect". Monthly Notices of the Royal Astronomical Society. 128 (4): 307-310. """ TD = backend.zeros_like(x) if shapiro_time_delay: potential = self.potential(x, y) TD = TD - potential if geometric_time_delay: ax, ay = self.reduced_deflection_angle(x, y) fp = 0.5 * (ax**2 + ay**2) TD = TD + fp factor = self._arcsec2_to_days() return factor * TD
@forward def _jacobian_deflection_angle_finitediff( self, x: ArrayLike, y: ArrayLike, pixelscale: ArrayLike, ) -> tuple[tuple[ArrayLike, ArrayLike], tuple[ArrayLike, ArrayLike]]: """ Return the jacobian of the deflection angle vector. This equates to a (2,2) matrix at each (x,y) point. """ # Compute deflection angles ax, ay = self.reduced_deflection_angle(x, y) # Build Jacobian J = backend.zeros((*ax.shape, 2, 2), device=ax.device, dtype=ax.dtype) grad_ax = backend.gradient(ax, spacing=pixelscale) grad_ay = backend.gradient(ay, spacing=pixelscale) J = backend.fill_at_indices(J, (Ellipsis, 0, 1), grad_ax[0]) J = backend.fill_at_indices(J, (Ellipsis, 0, 0), grad_ax[1]) J = backend.fill_at_indices(J, (Ellipsis, 1, 1), grad_ay[0]) J = backend.fill_at_indices(J, (Ellipsis, 1, 0), grad_ay[1]) return J @forward def _jacobian_deflection_angle_autograd( self, x: ArrayLike, y: ArrayLike, chunk_size: Optional[int] = None, ) -> tuple[tuple[ArrayLike, ArrayLike], tuple[ArrayLike, ArrayLike]]: """ Return the jacobian of the deflection angle vector. This equates to a (2,2) matrix at each (x,y) point. """ # Compute deflection angle gradients J = backend.vmap( backend.jacfwd( self.reduced_deflection_angle, argnums=(0, 1), randomness="different" ), chunk_size=chunk_size, )(backend.flatten(x), backend.flatten(y)) J = backend.stack([backend.stack(Jrow, dim=-1) for Jrow in J], dim=-2) return J.reshape(*x.shape, 2, 2)
[docs] @forward def jacobian_deflection_angle( self, x: ArrayLike, y: ArrayLike, method="autograd", pixelscale=None, chunk_size: Optional[int] = None, ) -> tuple[tuple[ArrayLike, ArrayLike], tuple[ArrayLike, ArrayLike]]: """ Return the jacobian of the deflection angle vector. This equates to a (2,2) matrix at each (x,y) point. method: autograd or fft """ if method == "autograd": return self._jacobian_deflection_angle_autograd(x, y, chunk_size) elif method == "finitediff": if pixelscale is None: raise ValueError( "Finite differences lensing jacobian requires regular grid " "and known pixelscale. Please include the pixelscale argument" ) return self._jacobian_deflection_angle_finitediff(x, y, pixelscale) else: raise ValueError("method should be one of: autograd, finitediff")
@forward def _jacobian_lens_equation_finitediff( self, x: ArrayLike, y: ArrayLike, pixelscale: ArrayLike, **kwargs, ) -> tuple[tuple[ArrayLike, ArrayLike], tuple[ArrayLike, ArrayLike]]: """ Return the jacobian of the lensing equation at specified points. This equates to a (2,2) matrix at each (x,y) point. """ # Build Jacobian J = self._jacobian_deflection_angle_finitediff(x, y, pixelscale, **kwargs) return backend.to(backend.eye(2), device=J.device) - J @forward def _jacobian_lens_equation_autograd( self, x: ArrayLike, y: ArrayLike, **kwargs, ) -> tuple[tuple[ArrayLike, ArrayLike], tuple[ArrayLike, ArrayLike]]: """ Return the jacobian of the lensing equation at specified points. This equates to a (2,2) matrix at each (x,y) point. """ # Build Jacobian J = self._jacobian_deflection_angle_autograd(x, y, **kwargs) return backend.to(backend.eye(2), device=J.device) - backend.detach(J)