Source code for caustics.light.sersic

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

from caskade import forward, Param

from .base import Source, NameType
from . import func
from ..angle_mixin import Angle_Mixin
from ..backend_obj import backend, ArrayLike

__all__ = ("Sersic",)


[docs] class Sersic(Angle_Mixin, Source): """ `Sersic` is a subclass of the abstract class `Source`. It represents a source in a strong gravitational lensing system that follows a Sersic profile, a mathematical function that describes how the intensity I of a galaxy varies with distance r from its center. The Sersic profile is often used to describe elliptical galaxies and spiral galaxies' bulges. Attributes ----------- x0: Optional[ArrayLike] The x-coordinate of the Sersic source's center. *Unit: arcsec* y0: Optional[ArrayLike] The y-coordinate of the Sersic source's center. *Unit: arcsec* q: Optional[ArrayLike] The axis ratio of the Sersic source. *Unit: unitless* phi: Optional[ArrayLike] The orientation of the Sersic source (position angle). *Unit: radians* n: Optional[ArrayLike] The Sersic index, which describes the degree of concentration of the source. *Unit: unitless* Re: Optional[ArrayLike] The scale length of the Sersic source. *Unit: arcsec* Ie: Optional[ArrayLike] The intensity at the effective radius. *Unit: flux* s: float A small constant for numerical stability. *Unit: arcsec* lenstronomy_k_mode: bool A flag indicating whether to use lenstronomy to compute the value of k. """ def __init__( self, x0: Annotated[ Optional[Union[ArrayLike, float]], "The x-coordinate of the Sersic source's center", True, ] = None, y0: Annotated[ Optional[Union[ArrayLike, float]], "The y-coordinate of the Sersic source's center", True, ] = None, q: Annotated[ Optional[Union[ArrayLike, float]], "The axis ratio of the Sersic source", True, ] = None, phi: Annotated[ Optional[Union[ArrayLike, float]], "The orientation of the Sersic source (position angle)", True, ] = None, n: Annotated[ Optional[Union[ArrayLike, float]], "The Sersic index, which describes the degree of concentration of the source", True, ] = None, Re: Annotated[ Optional[Union[ArrayLike, float]], "The scale length of the Sersic source", True, ] = None, Ie: Annotated[ Optional[Union[ArrayLike, float]], "The intensity at the effective radius", True, ] = None, s: Annotated[float, "A small constant for numerical stability"] = 0.0, use_lenstronomy_k: Annotated[ bool, "A flag indicating whether to use lenstronomy to compute the value of k.", ] = False, angle_system: str = "q_phi", e1: Optional[Union[ArrayLike, float]] = None, e2: Optional[Union[ArrayLike, float]] = None, c1: Optional[Union[ArrayLike, float]] = None, c2: Optional[Union[ArrayLike, float]] = None, name: NameType = None, ): """ Constructs the `Sersic` object with the given parameters. Parameters ---------- name: str The name of the source. x0: Optional[ArrayLike] The x-coordinate of the Sersic source's center. *Unit: arcsec* y0: Optional[ArrayLike] The y-coordinate of the Sersic source's center. *Unit: arcsec* q: Optional[ArrayLike] The axis ratio of the Sersic source. *Unit: unitless* phi: Optional[ArrayLike] The orientation of the Sersic source. *Unit: radians* n: Optional[ArrayLike] The Sersic index, which describes the degree of concentration of the source. *Unit: unitless* Re: Optional[ArrayLike] The scale length of the Sersic source. *Unit: arcsec* Ie: Optional[ArrayLike] The intensity at the effective radius. *Unit: flux* s: float A small constant for numerical stability. *Unit: arcsec* use_lenstronomy_k: bool A flag indicating whether to use lenstronomy to compute the value of k. """ super().__init__(name=name) self.x0 = Param("x0", x0, shape=(), units="arcsec") self.y0 = Param("y0", y0, shape=(), units="arcsec") self.q = Param("q", q, shape=(), units="unitless", valid=(0, 1)) self.phi = Param( "phi", phi, shape=(), units="radians", valid=(0, backend.pi), cyclic=True ) self.n = Param("n", n, shape=(), units="unitless", valid=(0.36, 10)) self.Re = Param("Re", Re, shape=(), units="arcsec", valid=(0, None)) self.Ie = Param("Ie", Ie, shape=(), units="flux", valid=(0, None)) self.s = s self.lenstronomy_k_mode = use_lenstronomy_k self.angle_system = angle_system if self.angle_system == "e1_e2": self.e1 = e1 self.e2 = e2 elif self.angle_system == "c1_c2": self.c1 = c1 self.c2 = c2
[docs] @forward def brightness( self, x, y, x0: Annotated[ArrayLike, "Param"], y0: Annotated[ArrayLike, "Param"], q: Annotated[ArrayLike, "Param"], phi: Annotated[ArrayLike, "Param"], n: Annotated[ArrayLike, "Param"], Re: Annotated[ArrayLike, "Param"], Ie: Annotated[ArrayLike, "Param"], ): """ Implements the `brightness` method for `Sersic`. The brightness at a given point is determined by the Sersic profile formula. Parameters ---------- x: ArrayLike The x-coordinate(s) at which to calculate the source brightness. This could be a single value or a tensor of values. *Unit: arcsec* y: ArrayLike The y-coordinate(s) at which to calculate the source brightness. This could be a single value or a tensor of values. *Unit: arcsec* Returns ------- ArrayLike The brightness of the source at the given point(s). The output tensor has the same shape as `x` and `y`. *Unit: flux* Notes ----- The Sersic profile is defined as: I(r) = Ie * exp(-k * ((r / r_e)^(1/n) - 1)), where Ie is the intensity at the effective radius r_e, n is the Sersic index that describes the concentration of the source, and k is a parameter that depends on n. In this implementation, we use elliptical coordinates ex and ey, and the transformation from Cartesian coordinates is handled by `to_elliptical`. The value of k can be calculated in two ways, controlled by `lenstronomy_k_mode`. If `lenstronomy_k_mode` is True, we use the approximation from Lenstronomy, otherwise, we use the approximation from Ciotti & Bertin (1999). """ if self.lenstronomy_k_mode: k = func.k_lenstronomy(n) else: k = func.k_sersic(n) return func.brightness_sersic(x0, y0, q, phi, n, Re, Ie, x, y, k, self.s)