Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions autogalaxy/profiles/basis.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
"""
The `Basis` class groups multiple light or mass profiles into a single object that acts like a single profile.

A basis is typically used for multi-component decompositions of a galaxy's light or mass distribution — for
example a multi-Gaussian expansion (MGE), which represents a galaxy as a sum of many Gaussian profiles, or
a shapelet decomposition. Each component of the basis captures a distinct spatial scale of the galaxy.

When linear light profiles are used in a basis, their individual intensities are solved simultaneously via a
linear inversion (a single matrix solve), making the inference highly efficient regardless of how many basis
components are included.
"""
import numpy as np
from typing import Dict, List, Optional, Union

Expand Down
43 changes: 42 additions & 1 deletion autogalaxy/profiles/geometry_profiles.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
"""
Geometry profiles define the spatial geometry of light and mass profiles, including their centre coordinates and
elliptical orientation.

The `GeometryProfile`, `SphProfile` and `EllProfile` classes provide the base geometric transformations that all
light and mass profiles inherit, including translating a grid to the profile centre and rotating it to the
profile's position angle.
"""
import numpy as np

from typing import Optional, Tuple, Type
Expand Down Expand Up @@ -34,14 +42,47 @@ def __eq__(self, other):

def has(self, cls: Type) -> bool:
"""
Does this instance have an attribute which is of type cls?
Returns `True` if any attribute of this profile is an instance of the input class `cls`, else `False`.

Parameters
----------
cls
The class type to search for amongst the profile's attributes.

Returns
-------
bool
`True` if any attribute is an instance of `cls`, else `False`.
"""
return aa.util.misc.has(values=self.__dict__.values(), cls=cls)

def transformed_to_reference_frame_grid_from(self, grid, xp=np, **kwargs):
"""
Transform a grid of (y,x) coordinates to the reference frame of the profile.

Subclasses override this to perform a translation to the profile centre and, for elliptical profiles,
a rotation to the profile's position angle.

Parameters
----------
grid
The (y, x) coordinates in the original reference frame of the grid.
"""
raise NotImplemented()

def transformed_from_reference_frame_grid_from(self, grid, xp=np, **kwargs):
"""
Transform a grid of (y,x) coordinates from the reference frame of the profile back to the original
observer reference frame.

Subclasses override this to reverse the translation to the profile centre and, for elliptical profiles,
the rotation to the profile's position angle.

Parameters
----------
grid
The (y, x) coordinates in the reference frame of the profile.
"""
raise NotImplemented()


Expand Down
30 changes: 30 additions & 0 deletions autogalaxy/profiles/light/abstract.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
"""
Abstract base classes for all light profiles in **PyAutoGalaxy**.

A light profile has an analytic function that describes the surface brightness of a galaxy as a function of
(y,x) Cartesian coordinates. Each profile is associated with a spatial geometry (centre and ellipticity) and
an `intensity` normalisation that scales the overall brightness.

The `LightProfile` class is the root of the light profile hierarchy. All concrete profiles (e.g. `Sersic`,
`Exponential`, `Gaussian`) inherit from it and implement `image_2d_from` and `image_2d_via_radii_from`.
"""
import numpy as np
from typing import Optional, Tuple

Expand Down Expand Up @@ -39,6 +49,13 @@ def __init__(

@property
def coefficient_tag(self) -> str:
"""
A short string tag used to label the intensity coefficient when this profile is used inside a `Basis`
object (e.g. for multi-Gaussian expansion or shapelet decomposition).

Returns an empty string for standard light profiles, and is overridden by linear light profile subclasses
to label their solved-for intensity coefficient.
"""
return ""

def image_2d_from(
Expand Down Expand Up @@ -114,9 +131,22 @@ def luminosity_integral(self, x: np.ndarray) -> np.ndarray:

@property
def half_light_radius(self) -> float:
"""
The radius that contains half of the total light of the profile (the half-light radius).

For profiles with an `effective_radius` attribute (e.g. Sersic, Exponential) this returns the
`effective_radius`. Returns `None` for profiles that do not define an effective radius.
"""
if hasattr(self, "effective_radius"):
return self.effective_radius

@property
def _intensity(self):
"""
The normalisation intensity of the light profile used internally when evaluating the image.

For standard light profiles this simply returns `self.intensity`. Linear light profiles override this
property to return 1.0, because their intensity is solved for via a linear inversion rather than being
a free parameter.
"""
return self.intensity
14 changes: 14 additions & 0 deletions autogalaxy/profiles/light/linear/abstract.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
"""
Abstract base classes for linear light profiles in **PyAutoGalaxy**.

A linear light profile behaves identically to a standard light profile (e.g. `Sersic`, `Exponential`,
`Gaussian`) except that its `intensity` is not a free parameter — instead, it is solved for analytically via
a linear inversion during every likelihood evaluation.

This reduces the number of non-linear parameters in a model fit by one per linear profile, which can
substantially speed up non-linear sampling without any loss of accuracy.

The `LightProfileLinear` class is the abstract base from which all linear light profile variants inherit.
The `LightProfileLinearObjFuncList` subclass additionally supports regularization, allowing the solved
intensities to be penalized by a smoothness prior.
"""
import inspect
import numpy as np
from typing import Dict, List, Optional
Expand Down
18 changes: 18 additions & 0 deletions autogalaxy/profiles/light/operated/abstract.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,20 @@
class LightProfileOperated:
"""
Mixin class that marks a light profile as already having had an instrument operation applied to it.

An "operated" light profile represents emission whose image has already had an operation applied, most
commonly a PSF convolution. This means that when the image of an operated light profile is computed, the
PSF convolution step is skipped — the PSF effect is already baked into the profile itself.

This pattern is useful for modelling point-source emission (e.g. AGN) or other compact emission where the
PSF profile itself is used directly as the light profile.

The `operated_only` input to `image_2d_from` methods throughout the codebase controls which light profiles
contribute to an image:

- `operated_only=None` (default): all light profiles contribute regardless of whether they are operated.
- `operated_only=True`: only `LightProfileOperated` instances contribute; non-operated profiles return zeros.
- `operated_only=False`: only non-operated profiles contribute; `LightProfileOperated` instances return zeros.
"""

pass
15 changes: 15 additions & 0 deletions autogalaxy/profiles/light/standard/sersic.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
"""
Sersic light profiles.

The Sersic profile is one of the most widely used models for describing the surface brightness of galaxies.
It has the functional form:

I(r) = I_eff * exp{ -b_n * [(r / r_eff)^(1/n) - 1] }

where `r_eff` is the effective (half-light) radius, `n` is the Sersic index controlling concentration, and
`b_n` is derived from `n` to ensure that `r_eff` encloses half the total flux.

Special cases: n=1 is the exponential (disk) profile, n=4 is the de Vaucouleurs (bulge) profile.

This module provides both elliptical (`Sersic`) and spherical (`SersicSph`) variants.
"""
import numpy as np

from numpy import seterr
Expand Down
146 changes: 146 additions & 0 deletions autogalaxy/profiles/mass/abstract/abstract.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
"""
Abstract base class for all mass profiles in **PyAutoGalaxy** and **PyAutoLens**.

A mass profile describes the projected mass distribution of a galaxy and exposes three fundamental lensing
quantities:

- `deflections_yx_2d_from` — the deflection angles α(θ) that describe how light rays are bent.
- `convergence_2d_from` — the dimensionless surface mass density κ(θ) = Σ(θ) / Σ_cr.
- `potential_2d_from` — the lensing (Shapiro) potential ψ(θ).

Every other lensing observable (shear, magnification, critical curves, Einstein radius, Fermat potential) can
be derived from these three quantities. See the `autogalaxy.operate.lens_calc` module for the `LensCalc` class
that derives these secondary quantities.
"""
import numpy as np
from typing import Tuple

Expand Down Expand Up @@ -25,9 +39,53 @@ def __init__(
super().__init__(centre=centre, ell_comps=ell_comps)

def deflections_yx_2d_from(self, grid):
"""
Returns the 2D deflection angles of the mass profile from a 2D grid of Cartesian (y,x) coordinates.

The deflection angle α(θ) at image-plane position θ describes how a light ray is bent by the
gravitational field of the lens. The source-plane position β is then:

β = θ − α(θ)

Deflection angles are the single most important output of a mass profile — every other lensing quantity
(convergence, shear, magnification, critical curves, caustics) can be derived from them.

Parameters
----------
grid
The 2D (y, x) coordinates where the deflection angles are evaluated.

Returns
-------
aa.VectorYX2D
The (y, x) deflection angles at every coordinate on the input grid.
"""
raise NotImplementedError

def deflections_2d_via_potential_2d_from(self, grid):
"""
Returns the 2D deflection angles of the mass profile by numerically differentiating the lensing
potential on the input grid.

This is a fallback implementation that computes deflection angles as the gradient of the potential via
finite differences:

α_y = ∂ψ/∂y, α_x = ∂ψ/∂x

Most concrete mass profiles override `deflections_yx_2d_from` with an analytic expression. This
method is provided for cross-checking and for profiles where only the potential is known analytically.

Parameters
----------
grid
The 2D (y, x) coordinates where the deflection angles are evaluated.

Returns
-------
aa.Grid2D
The (y, x) deflection angles at every coordinate on the input grid, computed via finite differences
of the lensing potential.
"""
potential = self.potential_2d_from(grid=grid)

deflections_y_2d = np.gradient(
Expand All @@ -43,22 +101,110 @@ def deflections_2d_via_potential_2d_from(self, grid):
)

def convergence_2d_from(self, grid, xp=np):
"""
Returns the 2D convergence of the mass profile from a 2D grid of Cartesian (y,x) coordinates.

The convergence κ(θ) is the dimensionless surface mass density of the lens, defined as the projected
surface mass density Σ(θ) divided by the critical surface mass density Σ_cr:

κ(θ) = Σ(θ) / Σ_cr

Physically, κ = 1 on the Einstein ring. Regions with κ > 1 produce multiple images.

Parameters
----------
grid
The 2D (y, x) coordinates where the convergence is evaluated.

Returns
-------
aa.Array2D
The convergence κ(θ) at every coordinate on the input grid.
"""
raise NotImplementedError

def convergence_func(self, grid_radius: float) -> float:
"""
Returns the convergence of the mass profile as a function of the radial coordinate.

This is used to integrate the convergence profile to compute enclosed masses and the Einstein radius.

Parameters
----------
grid_radius
The radial distance from the profile centre at which the convergence is evaluated.

Returns
-------
float
The convergence at the input radial distance.
"""
raise NotImplementedError

def potential_2d_from(self, grid):
"""
Returns the 2D lensing potential of the mass profile from a 2D grid of Cartesian (y,x) coordinates.

The lensing potential ψ(θ) is the gravitational (Shapiro) time-delay term. It quantifies how much the
passage of light through the gravitational field delays its arrival relative to a straight-line path in
empty space.

The potential enters directly into the Fermat potential:

φ(θ) = ½ |θ − β|² − ψ(θ)

which governs time delays between multiple lensed images of the same source.

Parameters
----------
grid
The 2D (y, x) coordinates where the lensing potential is evaluated.

Returns
-------
aa.Array2D
The lensing potential ψ(θ) at every coordinate on the input grid.
"""
raise NotImplementedError

def potential_func(self, u, y, x):
"""
Returns the integrand of the lensing potential at a single point, used in numerical integration schemes
for computing the potential from the mass profile's convergence.

Parameters
----------
u
The integration variable.
y
The y-coordinate of the point at which the potential is evaluated.
x
The x-coordinate of the point at which the potential is evaluated.
"""
raise NotImplementedError

def mass_integral(self, x, xp=np):
"""
Integrand used by `mass_angular_within_circle_from` to compute the total projected mass within a circle.

This integrates 2π r κ(r) to give the enclosed convergence (dimensionless mass) at radius `x`.

Parameters
----------
x
The radial coordinate at which the integrand is evaluated.
"""
return 2 * xp.pi * x * self.convergence_func(grid_radius=aa.ArrayIrregular(x))

@property
def ellipticity_rescale(self):
"""
A rescaling factor applied to account for the ellipticity of the mass profile when computing the
Einstein radius from the average convergence equals unity criterion.

For a spherical profile this is 1.0. Elliptical profiles return a factor that maps the elliptical
enclosed mass to an equivalent circular Einstein radius.
"""
return NotImplementedError()

def mass_angular_within_circle_from(self, radius: float):
Expand Down
Loading