# 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)