Source code for caustics.lenses.tnfw

# mypy: disable-error-code="operator,union-attr,dict-item"
from typing import Optional, Union, Annotated

from caskade import forward, Param

from .base import ThinLens, CosmologyType, NameType, ZType
from . import func
from ..backend_obj import ArrayLike

DELTA = 200.0

__all__ = ("TNFW",)


[docs] class TNFW(ThinLens): """Truncated Navaro-Frenk-White profile TNFW lens class. This class models a lens using the truncated Navarro-Frenk-White (NFW) profile. The NFW profile is a spatial density profile of dark matter halo that arises in cosmological simulations. It is truncated with an extra scaling term which smoothly reduces the density such that it does not diverge to infinity. This is based off the paper by Baltz et al. 2009: https://arxiv.org/abs/0705.0682 https://ui.adsabs.harvard.edu/abs/2009JCAP...01..015B/abstract Notes ------ The mass `m` in the TNFW profile corresponds to the total mass of the lens. This is different from the NFW profile where the mass `m` parameter corresponds to the mass within R200. If you prefer the "mass within R200" version you can set: `interpret_m_total_mass = False` on initialization of the object. However, the mass within R200 will be computed for an NFW profile, not a TNFW profile. This is in line with how lenstronomy interprets the mass parameter. Parameters ----- name: string Name of the lens instance. cosmology: Cosmology An instance of the Cosmology class which contains information about the cosmological model and parameters. z_l: Optional[ArrayLike] Redshift of the lens. *Unit: unitless* z_s: Optional[ArrayLike] Redshift of the source. *Unit: unitless* x0: Optional[ArrayLike] Center of lens position on x-axis. *Unit: arcsec* y0: Optional[ArrayLike] Center of lens position on y-axis. *Unit: arcsec* mass: Optional[ArrayLike] Mass of the lens. *Unit: Msun* Rs: Optional[ArrayLike] Scale radius of the TNFW lens. *Unit: arcsec* tau: Optional[ArrayLike] Truncation scale. Ratio of truncation radius to scale radius. *Unit: unitless* s: float Softening parameter to avoid singularities at the center of the lens. Default is 0.0. *Unit: arcsec* interpret_m_total_mass: boolean Indicates how to interpret the mass variable "m". If true the mass is interpreted as the total mass of the halo (good because it makes sense). If false it is interpreted as what the mass would have been within R200 of a an NFW that isn't truncated (good because it is easily compared with an NFW). """ _null_params = { "x0": 0.0, "y0": 0.0, "mass": 1e13, "Rs": 1.0, "tau": 3.0, } def __init__( self, cosmology: CosmologyType, z_l: ZType = None, z_s: ZType = None, x0: Annotated[ Optional[Union[ArrayLike, float]], "Center of lens position on x-axis", True, "arcsec", ] = None, y0: Annotated[ Optional[Union[ArrayLike, float]], "Center of lens position on y-axis", True, "arcsec", ] = None, mass: Annotated[ Optional[Union[ArrayLike, float]], "Mass of the lens", True, "Msol" ] = None, Rs: Annotated[ Optional[Union[ArrayLike, float]], "Scale radius of the TNFW lens", True, "arcsec", ] = None, tau: Annotated[ Optional[Union[ArrayLike, float]], "Truncation scale. Ratio of truncation radius to scale radius", True, "rt/rs", ] = None, s: Annotated[ float, "Softening parameter to avoid singularities at the center of the lens", ] = 0.0, interpret_m_total_mass: Annotated[ bool, "Indicates how to interpret the mass variable 'm'" ] = True, name: NameType = None, ): """ Initialize an instance of the TNFW lens class. """ super().__init__(cosmology, z_l, name=name, z_s=z_s) self.x0 = Param("x0", x0, shape=(), units="arcsec") self.y0 = Param("y0", y0, shape=(), units="arcsec") self.mass = Param("mass", mass, shape=(), units="Msun", valid=(0, None)) self.Rs = Param("Rs", Rs, shape=(), units="arcsec", valid=(0, None)) self.tau = Param("tau", tau, shape=(), units="unitless", valid=(0, None)) self.s = s self.interpret_m_total_mass = interpret_m_total_mass
[docs] @forward def get_concentration( self, z_l: Annotated[ArrayLike, "Param"], mass: Annotated[ArrayLike, "Param"], Rs: Annotated[ArrayLike, "Param"], ) -> ArrayLike: """ Compute the concentration parameter "c" for a TNFW profile. Parameters ---------- z_l: ArrayLike Redshift of the lens. *Unit: unitless* x0: ArrayLike Center of lens position on x-axis. *Unit: arcsec* y0: ArrayLike Center of lens position on y-axis. *Unit: arcsec* mass: Optional[ArrayLike] Mass of the lens. *Unit: Msun* Rs: Optional[ArrayLike] Scale radius of the TNFW lens. *Unit: arcsec* tau: Optional[ArrayLike] Truncation scale. Ratio of truncation radius to scale radius. *Unit: unitless* Returns ------- ArrayLike The concentration parameter "c" for a TNFW profile. *Unit: unitless* """ critical_density = self.cosmology.critical_density(z_l) d_l = self.cosmology.angular_diameter_distance(z_l) return func.concentration_tnfw(mass, Rs, critical_density, d_l, DELTA)
[docs] @forward def get_truncation_radius( self, Rs: Annotated[ArrayLike, "Param"], tau: Annotated[ArrayLike, "Param"], ) -> ArrayLike: """ Calculate the truncation radius of the TNFW lens. Returns ------- ArrayLike The truncation radius of the lens. *Unit: arcsec* """ return tau * Rs
[docs] @forward def M0( self, z_l: Annotated[ArrayLike, "Param"], mass: Annotated[ArrayLike, "Param"], Rs: Annotated[ArrayLike, "Param"], tau: Annotated[ArrayLike, "Param"], ) -> ArrayLike: """ Calculate the reference mass. This is an abstract reference mass used internally in the equations from Baltz et al. 2009. Returns ------- ArrayLike The reference mass of the lens in Msun. *Unit: Msun* """ if self.interpret_m_total_mass: return func.M0_totmass_tnfw(mass, tau) else: d_l = self.cosmology.angular_diameter_distance(z_l) critical_density = self.cosmology.critical_density(z_l) c = func.concentration_tnfw(mass, Rs, critical_density, d_l, DELTA) return func.M0_scalemass_tnfw(Rs, c, critical_density, d_l, DELTA)
[docs] @forward def get_scale_density( self, z_l: Annotated[ArrayLike, "Param"], mass: Annotated[ArrayLike, "Param"], Rs: Annotated[ArrayLike, "Param"], ) -> ArrayLike: """ Calculate the scale density of the lens. Returns -------- ArrayLike The scale density of the lens. *Unit: Msun/Mpc^3* """ d_l = self.cosmology.angular_diameter_distance(z_l) critical_density = self.cosmology.critical_density(z_l) c = func.concentration_tnfw(mass, Rs, critical_density, d_l, DELTA) return func.scale_density_tnfw(c, critical_density, DELTA)
[docs] @forward def convergence( self, x: ArrayLike, y: ArrayLike, z_s: Annotated[ArrayLike, "Param"], z_l: Annotated[ArrayLike, "Param"], x0: Annotated[ArrayLike, "Param"], y0: Annotated[ArrayLike, "Param"], mass: Annotated[ArrayLike, "Param"], Rs: Annotated[ArrayLike, "Param"], tau: Annotated[ArrayLike, "Param"], ) -> ArrayLike: """ TNFW convergence as given in Baltz et al. 2009. This is unitless since it is Sigma(x) / Sigma_crit. Parameters ---------- x: ArrayLike The x-coordinate on the lens plane. *Unit: arcsec* y: ArrayLike The y-coordinate on the lens plane. *Unit: arcsec* Returns --------- ArrayLike Convergence at requested position. *Unit: unitless* """ d_l = self.cosmology.angular_diameter_distance(z_l) critical_density = self.cosmology.critical_surface_density(z_l, z_s) M0 = self.M0(z_l=z_l, mass=mass, Rs=Rs, tau=tau) return func.convergence_tnfw( x0, y0, Rs, tau, x, y, critical_density, M0, d_l, self.s, )
[docs] @forward def mass_enclosed_2d( self, r: ArrayLike, Rs: Annotated[ArrayLike, "Param"], tau: Annotated[ArrayLike, "Param"], ) -> ArrayLike: """ Total projected mass (Msun) within a radius r (arcsec). Parameters ----------- r: ArrayLike Radius within which to calculate the mass. *Unit: arcsec* Returns ------- ArrayLike Integrated mass projected in infinite cylinder within radius r. *Unit: Msun* """ M0 = self.M0() return func.mass_enclosed_2d_tnfw(r, Rs, tau, M0)
[docs] @forward def physical_deflection_angle( self, x: ArrayLike, y: ArrayLike, z_l: Annotated[ArrayLike, "Param"], x0: Annotated[ArrayLike, "Param"], y0: Annotated[ArrayLike, "Param"], mass: Annotated[ArrayLike, "Param"], Rs: Annotated[ArrayLike, "Param"], tau: Annotated[ArrayLike, "Param"], ) -> tuple[ArrayLike, ArrayLike]: """Compute the physical deflection angle (arcsec) for this lens at the requested position. Note that the NFW/TNFW profile is more naturally represented as a physical deflection angle, this is easily internally converted to a reduced deflection angle. Parameters ---------- x: ArrayLike The x-coordinate on the lens plane. *Unit: arcsec* y: ArrayLike The y-coordinate on 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_l = self.cosmology.angular_diameter_distance(z_l) M0 = self.M0(z_l=z_l, mass=mass, Rs=Rs, tau=tau) return func.physical_deflection_angle_tnfw( x0, y0, Rs, tau, x, y, M0, d_l, self.s )
[docs] @forward def reduced_deflection_angle(self, x, y, z_s, z_l): 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.physical_deflection_angle(x, y) return func.reduced_from_physical_deflection_angle( deflection_angle_x, deflection_angle_y, d_s, d_ls )
[docs] @forward def potential( self, x: ArrayLike, y: ArrayLike, z_s: Annotated[ArrayLike, "Param"], z_l: Annotated[ArrayLike, "Param"], x0: Annotated[ArrayLike, "Param"], y0: Annotated[ArrayLike, "Param"], mass: Annotated[ArrayLike, "Param"], Rs: Annotated[ArrayLike, "Param"], tau: Annotated[ArrayLike, "Param"], ) -> ArrayLike: """ Compute the lensing potential. Note that this is not a unitless potential! This is the potential as given in Baltz et al. 2009. TODO: convert to dimensionless potential. Parameters ----------- x: ArrayLike x-coordinate in the lens plane. *Unit: arcsec* y: ArrayLike y-coordinate in the lens plane. *Unit: arcsec* Returns ------- ArrayLike The lensing potential. *Unit: arcsec^2* """ 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) M0 = self.M0(z_l=z_l, mass=mass, Rs=Rs, tau=tau) return func.potential_tnfw(x0, y0, Rs, tau, x, y, M0, d_l, d_s, d_ls, self.s)