# mypy: disable-error-code="operator,dict-item"
from typing import Optional, Union, Annotated, Literal
from warnings import warn
from caskade import forward, Param
from ..backend_obj import ArrayLike
from .base import ThinLens, CosmologyType, NameType, ZType
from . import func
__all__ = ("Point",)
[docs]
class Point(ThinLens):
"""
Class representing a point mass lens in strong gravitational lensing.
Attributes
----------
name: str
The name of the point lens.
cosmology: Cosmology
The cosmology used for calculations.
z_l: Optional[Union[ArrayLike, float]]
Redshift of the lens.
*Unit: unitless*
x0: Optional[Union[ArrayLike, float]]
x-coordinate of the center of the lens.
*Unit: arcsec*
y0: Optional[Union[ArrayLike, float]]
y-coordinate of the center of the lens.
*Unit: arcsec*
Rein: Optional[Union[ArrayLike, float]]
Einstein radius of the lens.
*Unit: arcsec*
s: float
Softening parameter to prevent numerical instabilities.
*Unit: arcsec*
"""
_null_params = {
"x0": 0.0,
"y0": 0.0,
"Rein": 1.0,
}
def __init__(
self,
cosmology: CosmologyType,
z_l: ZType = None,
z_s: ZType = None,
x0: Annotated[
Optional[Union[ArrayLike, float]],
"X coordinate of the center of the lens",
True,
] = None,
y0: Annotated[
Optional[Union[ArrayLike, float]],
"Y coordinate of the center of the lens",
True,
] = None,
Rein: Annotated[
Optional[Union[ArrayLike, float]], "Einstein radius of the lens", True
] = None,
parametrization: Literal["Rein", "mass"] = "Rein",
s: Annotated[
float, "Softening parameter to prevent numerical instabilities"
] = 0.0,
name: NameType = None,
**kwargs,
):
"""
Initialize the Point class.
Parameters
----------
name: string
The name of the point lens.
cosmology: Cosmology
The cosmology used for calculations.
z_l: Optional[ArrayLike]
Redshift of the lens.
*Unit: unitless*
x0: Optional[ArrayLike]
x-coordinate of the center of the lens.
*Unit: arcsec*
y0: Optional[ArrayLike]
y-coordinate of the center of the lens.
*Unit: arcsec*
Rein: Optional[ArrayLike]
Einstein radius of the lens.
*Unit: arcsec*
s: float
Softening parameter to prevent numerical instabilities.
*Unit: arcsec*
"""
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.Rein = Param("Rein", Rein, shape=(), units="arcsec", valid=(0, None))
self._parametrization = "Rein"
self.parametrization = parametrization
if self.parametrization == "mass":
self.mass.value = kwargs.get("mass", None)
self.s = s
@property
def parametrization(self):
return self._parametrization
@parametrization.setter
def parametrization(self, value: str):
if value not in ["Rein", "mass"]:
raise ValueError(
f"Invalid parametrization {value}. Choose from ['Rein', 'mass']"
)
if value == "mass" and self.parametrization != "mass":
self.mass = Param(
"mass", shape=self.Rein.shape if self.Rein.static else (), units="Msol"
)
def mass_to_rein(p):
Dls = p["cosmology"].angular_diameter_distance_z1z2(
p["z_l"].value, p["z_s"].value
)
Dl = p["cosmology"].angular_diameter_distance(p["z_l"].value)
Ds = p["cosmology"].angular_diameter_distance(p["z_s"].value)
return func.mass_to_rein_point(p["mass"].value, Dls, Dl, Ds)
if self.Rein.static:
warn(
f"Parameter {self.Rein.name} was static, value now overridden by new {value} parametrization. To remove this warning, have {self.Rein.name} be dynamic when changing parametrizations.",
)
self.Rein.value = mass_to_rein
self.Rein.link(self.mass)
self.Rein.link(self.z_s)
self.Rein.link(self.z_l)
self.Rein.link("cosmology", self.cosmology)
if value == "Rein" and self.parametrization != "Rein":
try:
self.Rein.value = None
if self.mass.static:
warn(
f"Parameter {self.mass.name} was static, value now overridden by new {value} parametrization. To remove this warning, have {self.mass.name} be dynamic when changing parametrizations.",
)
del self.mass
except AttributeError:
pass
self._parametrization = value
[docs]
@forward
def mass_to_rein(
self,
mass: ArrayLike,
z_s: Annotated[ArrayLike, "Param"],
z_l: Annotated[ArrayLike, "Param"],
) -> ArrayLike:
"""
Convert mass to the Einstein radius.
Parameters
----------
mass: ArrayLike
The mass of the lens
*Unit: solar mass*
Returns
-------
ArrayLike
The Einstein radius.
*Unit: arcsec*
"""
Dls = self.cosmology.angular_diameter_distance_z1z2(z_l, z_s)
Dl = self.cosmology.angular_diameter_distance(z_l)
Ds = self.cosmology.angular_diameter_distance(z_s)
return func.mass_to_rein_point(mass, Dls, Dl, Ds)
[docs]
@forward
def rein_to_mass(
self,
r: ArrayLike,
z_s: Annotated[ArrayLike, "Param"],
z_l: Annotated[ArrayLike, "Param"],
) -> ArrayLike:
"""
Convert Einstein radius to mass.
Parameters
----------
r: ArrayLike
The Einstein radius.
*Unit: arcsec*
Returns
-------
ArrayLike
The mass of the lens
*Unit: solar mass*
"""
Dls = self.cosmology.angular_diameter_distance_z1z2(z_l, z_s)
Dl = self.cosmology.angular_diameter_distance(z_l)
Ds = self.cosmology.angular_diameter_distance(z_s)
return func.rein_to_mass_point(r, Dls, Dl, Ds)
[docs]
@forward
def reduced_deflection_angle(
self,
x: ArrayLike,
y: ArrayLike,
x0: Annotated[ArrayLike, "Param"],
y0: Annotated[ArrayLike, "Param"],
Rein: Annotated[ArrayLike, "Param"],
) -> tuple[ArrayLike, ArrayLike]:
"""
Compute the deflection angles.
Parameters
----------
x: ArrayLike
x-coordinates in the lens plane.
*Unit: arcsec*
y: ArrayLike
y-coordinates in the lens plane.
*Unit: arcsec*
Returns
-------
x_component: ArrayLike
Deflection Angle in the x-direction.
*Unit: arcsec*
y_component: ArrayLike
Deflection Angle in the y-direction.
*Unit: arcsec*
"""
return func.reduced_deflection_angle_point(x0, y0, Rein, x, y, self.s)
[docs]
@forward
def potential(
self,
x: ArrayLike,
y: ArrayLike,
x0: Annotated[ArrayLike, "Param"],
y0: Annotated[ArrayLike, "Param"],
Rein: Annotated[ArrayLike, "Param"],
) -> ArrayLike:
"""
Compute the lensing potential.
Parameters
----------
x: ArrayLike
x-coordinates in the lens plane.
*Unit: arcsec*
y: ArrayLike
y-coordinates in the lens plane.
*Unit: arcsec*
Returns
-------
ArrayLike
The lensing potential.
*Unit: arcsec^2*
"""
return func.potential_point(x0, y0, Rein, x, y, self.s)
[docs]
@forward
def convergence(
self,
x: ArrayLike,
y: ArrayLike,
x0: Annotated[ArrayLike, "Param"],
y0: Annotated[ArrayLike, "Param"],
) -> ArrayLike:
"""
Compute the convergence (dimensionless surface mass density).
Parameters
----------
x: ArrayLike
x-coordinates in the lens plane.
*Unit: arcsec*
y: ArrayLike
y-coordinates in the lens plane.
*Unit: arcsec*
Returns
--------
ArrayLike
The convergence (dimensionless surface mass density).
*Unit: unitless*
"""
return func.convergence_point(x0, y0, x, y)