Skip to content

Commit 91c39d6

Browse files
Jammy2211Jammy2211claude
authored
docs: add module docstrings and improve method docstrings in profiles package (#292)
- Add module-level docstrings to geometry_profiles, light/abstract, light/standard/sersic, light/linear/abstract, light/operated/abstract, mass/abstract/abstract, and basis explaining what each module contains - Add class docstring to LightProfileOperated explaining the operated-only filtering pattern used throughout the codebase - Add docstrings to abstract MassProfile methods (deflections_yx_2d_from, convergence_2d_from, potential_2d_from, convergence_func, potential_func, mass_integral, ellipticity_rescale) including the lensing physics context - Add deflections_2d_via_potential_2d_from docstring explaining the finite-difference fallback implementation - Improve GeometryProfile.has() docstring with Parameters/Returns sections - Add docstrings to GeometryProfile abstract transform methods - Add docstrings to LightProfile.coefficient_tag, half_light_radius, _intensity Co-authored-by: Jammy2211 <JNightingale2211@gmail.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent f430d70 commit 91c39d6

7 files changed

Lines changed: 276 additions & 1 deletion

File tree

autogalaxy/profiles/basis.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
"""
2+
The `Basis` class groups multiple light or mass profiles into a single object that acts like a single profile.
3+
4+
A basis is typically used for multi-component decompositions of a galaxy's light or mass distribution — for
5+
example a multi-Gaussian expansion (MGE), which represents a galaxy as a sum of many Gaussian profiles, or
6+
a shapelet decomposition. Each component of the basis captures a distinct spatial scale of the galaxy.
7+
8+
When linear light profiles are used in a basis, their individual intensities are solved simultaneously via a
9+
linear inversion (a single matrix solve), making the inference highly efficient regardless of how many basis
10+
components are included.
11+
"""
112
import numpy as np
213
from typing import Dict, List, Optional, Union
314

autogalaxy/profiles/geometry_profiles.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
"""
2+
Geometry profiles define the spatial geometry of light and mass profiles, including their centre coordinates and
3+
elliptical orientation.
4+
5+
The `GeometryProfile`, `SphProfile` and `EllProfile` classes provide the base geometric transformations that all
6+
light and mass profiles inherit, including translating a grid to the profile centre and rotating it to the
7+
profile's position angle.
8+
"""
19
import numpy as np
210

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

3543
def has(self, cls: Type) -> bool:
3644
"""
37-
Does this instance have an attribute which is of type cls?
45+
Returns `True` if any attribute of this profile is an instance of the input class `cls`, else `False`.
46+
47+
Parameters
48+
----------
49+
cls
50+
The class type to search for amongst the profile's attributes.
51+
52+
Returns
53+
-------
54+
bool
55+
`True` if any attribute is an instance of `cls`, else `False`.
3856
"""
3957
return aa.util.misc.has(values=self.__dict__.values(), cls=cls)
4058

4159
def transformed_to_reference_frame_grid_from(self, grid, xp=np, **kwargs):
60+
"""
61+
Transform a grid of (y,x) coordinates to the reference frame of the profile.
62+
63+
Subclasses override this to perform a translation to the profile centre and, for elliptical profiles,
64+
a rotation to the profile's position angle.
65+
66+
Parameters
67+
----------
68+
grid
69+
The (y, x) coordinates in the original reference frame of the grid.
70+
"""
4271
raise NotImplemented()
4372

4473
def transformed_from_reference_frame_grid_from(self, grid, xp=np, **kwargs):
74+
"""
75+
Transform a grid of (y,x) coordinates from the reference frame of the profile back to the original
76+
observer reference frame.
77+
78+
Subclasses override this to reverse the translation to the profile centre and, for elliptical profiles,
79+
the rotation to the profile's position angle.
80+
81+
Parameters
82+
----------
83+
grid
84+
The (y, x) coordinates in the reference frame of the profile.
85+
"""
4586
raise NotImplemented()
4687

4788

autogalaxy/profiles/light/abstract.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
"""
2+
Abstract base classes for all light profiles in **PyAutoGalaxy**.
3+
4+
A light profile has an analytic function that describes the surface brightness of a galaxy as a function of
5+
(y,x) Cartesian coordinates. Each profile is associated with a spatial geometry (centre and ellipticity) and
6+
an `intensity` normalisation that scales the overall brightness.
7+
8+
The `LightProfile` class is the root of the light profile hierarchy. All concrete profiles (e.g. `Sersic`,
9+
`Exponential`, `Gaussian`) inherit from it and implement `image_2d_from` and `image_2d_via_radii_from`.
10+
"""
111
import numpy as np
212
from typing import Optional, Tuple
313

@@ -39,6 +49,13 @@ def __init__(
3949

4050
@property
4151
def coefficient_tag(self) -> str:
52+
"""
53+
A short string tag used to label the intensity coefficient when this profile is used inside a `Basis`
54+
object (e.g. for multi-Gaussian expansion or shapelet decomposition).
55+
56+
Returns an empty string for standard light profiles, and is overridden by linear light profile subclasses
57+
to label their solved-for intensity coefficient.
58+
"""
4259
return ""
4360

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

115132
@property
116133
def half_light_radius(self) -> float:
134+
"""
135+
The radius that contains half of the total light of the profile (the half-light radius).
136+
137+
For profiles with an `effective_radius` attribute (e.g. Sersic, Exponential) this returns the
138+
`effective_radius`. Returns `None` for profiles that do not define an effective radius.
139+
"""
117140
if hasattr(self, "effective_radius"):
118141
return self.effective_radius
119142

120143
@property
121144
def _intensity(self):
145+
"""
146+
The normalisation intensity of the light profile used internally when evaluating the image.
147+
148+
For standard light profiles this simply returns `self.intensity`. Linear light profiles override this
149+
property to return 1.0, because their intensity is solved for via a linear inversion rather than being
150+
a free parameter.
151+
"""
122152
return self.intensity

autogalaxy/profiles/light/linear/abstract.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
"""
2+
Abstract base classes for linear light profiles in **PyAutoGalaxy**.
3+
4+
A linear light profile behaves identically to a standard light profile (e.g. `Sersic`, `Exponential`,
5+
`Gaussian`) except that its `intensity` is not a free parameter — instead, it is solved for analytically via
6+
a linear inversion during every likelihood evaluation.
7+
8+
This reduces the number of non-linear parameters in a model fit by one per linear profile, which can
9+
substantially speed up non-linear sampling without any loss of accuracy.
10+
11+
The `LightProfileLinear` class is the abstract base from which all linear light profile variants inherit.
12+
The `LightProfileLinearObjFuncList` subclass additionally supports regularization, allowing the solved
13+
intensities to be penalized by a smoothness prior.
14+
"""
115
import inspect
216
import numpy as np
317
from typing import Dict, List, Optional
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,20 @@
11
class LightProfileOperated:
2+
"""
3+
Mixin class that marks a light profile as already having had an instrument operation applied to it.
4+
5+
An "operated" light profile represents emission whose image has already had an operation applied, most
6+
commonly a PSF convolution. This means that when the image of an operated light profile is computed, the
7+
PSF convolution step is skipped — the PSF effect is already baked into the profile itself.
8+
9+
This pattern is useful for modelling point-source emission (e.g. AGN) or other compact emission where the
10+
PSF profile itself is used directly as the light profile.
11+
12+
The `operated_only` input to `image_2d_from` methods throughout the codebase controls which light profiles
13+
contribute to an image:
14+
15+
- `operated_only=None` (default): all light profiles contribute regardless of whether they are operated.
16+
- `operated_only=True`: only `LightProfileOperated` instances contribute; non-operated profiles return zeros.
17+
- `operated_only=False`: only non-operated profiles contribute; `LightProfileOperated` instances return zeros.
18+
"""
19+
220
pass

autogalaxy/profiles/light/standard/sersic.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1+
"""
2+
Sersic light profiles.
3+
4+
The Sersic profile is one of the most widely used models for describing the surface brightness of galaxies.
5+
It has the functional form:
6+
7+
I(r) = I_eff * exp{ -b_n * [(r / r_eff)^(1/n) - 1] }
8+
9+
where `r_eff` is the effective (half-light) radius, `n` is the Sersic index controlling concentration, and
10+
`b_n` is derived from `n` to ensure that `r_eff` encloses half the total flux.
11+
12+
Special cases: n=1 is the exponential (disk) profile, n=4 is the de Vaucouleurs (bulge) profile.
13+
14+
This module provides both elliptical (`Sersic`) and spherical (`SersicSph`) variants.
15+
"""
116
import numpy as np
217

318
from numpy import seterr

autogalaxy/profiles/mass/abstract/abstract.py

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
"""
2+
Abstract base class for all mass profiles in **PyAutoGalaxy** and **PyAutoLens**.
3+
4+
A mass profile describes the projected mass distribution of a galaxy and exposes three fundamental lensing
5+
quantities:
6+
7+
- `deflections_yx_2d_from` — the deflection angles α(θ) that describe how light rays are bent.
8+
- `convergence_2d_from` — the dimensionless surface mass density κ(θ) = Σ(θ) / Σ_cr.
9+
- `potential_2d_from` — the lensing (Shapiro) potential ψ(θ).
10+
11+
Every other lensing observable (shear, magnification, critical curves, Einstein radius, Fermat potential) can
12+
be derived from these three quantities. See the `autogalaxy.operate.lens_calc` module for the `LensCalc` class
13+
that derives these secondary quantities.
14+
"""
115
import numpy as np
216
from typing import Tuple
317

@@ -25,9 +39,53 @@ def __init__(
2539
super().__init__(centre=centre, ell_comps=ell_comps)
2640

2741
def deflections_yx_2d_from(self, grid):
42+
"""
43+
Returns the 2D deflection angles of the mass profile from a 2D grid of Cartesian (y,x) coordinates.
44+
45+
The deflection angle α(θ) at image-plane position θ describes how a light ray is bent by the
46+
gravitational field of the lens. The source-plane position β is then:
47+
48+
β = θ − α(θ)
49+
50+
Deflection angles are the single most important output of a mass profile — every other lensing quantity
51+
(convergence, shear, magnification, critical curves, caustics) can be derived from them.
52+
53+
Parameters
54+
----------
55+
grid
56+
The 2D (y, x) coordinates where the deflection angles are evaluated.
57+
58+
Returns
59+
-------
60+
aa.VectorYX2D
61+
The (y, x) deflection angles at every coordinate on the input grid.
62+
"""
2863
raise NotImplementedError
2964

3065
def deflections_2d_via_potential_2d_from(self, grid):
66+
"""
67+
Returns the 2D deflection angles of the mass profile by numerically differentiating the lensing
68+
potential on the input grid.
69+
70+
This is a fallback implementation that computes deflection angles as the gradient of the potential via
71+
finite differences:
72+
73+
α_y = ∂ψ/∂y, α_x = ∂ψ/∂x
74+
75+
Most concrete mass profiles override `deflections_yx_2d_from` with an analytic expression. This
76+
method is provided for cross-checking and for profiles where only the potential is known analytically.
77+
78+
Parameters
79+
----------
80+
grid
81+
The 2D (y, x) coordinates where the deflection angles are evaluated.
82+
83+
Returns
84+
-------
85+
aa.Grid2D
86+
The (y, x) deflection angles at every coordinate on the input grid, computed via finite differences
87+
of the lensing potential.
88+
"""
3189
potential = self.potential_2d_from(grid=grid)
3290

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

45103
def convergence_2d_from(self, grid, xp=np):
104+
"""
105+
Returns the 2D convergence of the mass profile from a 2D grid of Cartesian (y,x) coordinates.
106+
107+
The convergence κ(θ) is the dimensionless surface mass density of the lens, defined as the projected
108+
surface mass density Σ(θ) divided by the critical surface mass density Σ_cr:
109+
110+
κ(θ) = Σ(θ) / Σ_cr
111+
112+
Physically, κ = 1 on the Einstein ring. Regions with κ > 1 produce multiple images.
113+
114+
Parameters
115+
----------
116+
grid
117+
The 2D (y, x) coordinates where the convergence is evaluated.
118+
119+
Returns
120+
-------
121+
aa.Array2D
122+
The convergence κ(θ) at every coordinate on the input grid.
123+
"""
46124
raise NotImplementedError
47125

48126
def convergence_func(self, grid_radius: float) -> float:
127+
"""
128+
Returns the convergence of the mass profile as a function of the radial coordinate.
129+
130+
This is used to integrate the convergence profile to compute enclosed masses and the Einstein radius.
131+
132+
Parameters
133+
----------
134+
grid_radius
135+
The radial distance from the profile centre at which the convergence is evaluated.
136+
137+
Returns
138+
-------
139+
float
140+
The convergence at the input radial distance.
141+
"""
49142
raise NotImplementedError
50143

51144
def potential_2d_from(self, grid):
145+
"""
146+
Returns the 2D lensing potential of the mass profile from a 2D grid of Cartesian (y,x) coordinates.
147+
148+
The lensing potential ψ(θ) is the gravitational (Shapiro) time-delay term. It quantifies how much the
149+
passage of light through the gravitational field delays its arrival relative to a straight-line path in
150+
empty space.
151+
152+
The potential enters directly into the Fermat potential:
153+
154+
φ(θ) = ½ |θ − β|² − ψ(θ)
155+
156+
which governs time delays between multiple lensed images of the same source.
157+
158+
Parameters
159+
----------
160+
grid
161+
The 2D (y, x) coordinates where the lensing potential is evaluated.
162+
163+
Returns
164+
-------
165+
aa.Array2D
166+
The lensing potential ψ(θ) at every coordinate on the input grid.
167+
"""
52168
raise NotImplementedError
53169

54170
def potential_func(self, u, y, x):
171+
"""
172+
Returns the integrand of the lensing potential at a single point, used in numerical integration schemes
173+
for computing the potential from the mass profile's convergence.
174+
175+
Parameters
176+
----------
177+
u
178+
The integration variable.
179+
y
180+
The y-coordinate of the point at which the potential is evaluated.
181+
x
182+
The x-coordinate of the point at which the potential is evaluated.
183+
"""
55184
raise NotImplementedError
56185

57186
def mass_integral(self, x, xp=np):
187+
"""
188+
Integrand used by `mass_angular_within_circle_from` to compute the total projected mass within a circle.
189+
190+
This integrates 2π r κ(r) to give the enclosed convergence (dimensionless mass) at radius `x`.
191+
192+
Parameters
193+
----------
194+
x
195+
The radial coordinate at which the integrand is evaluated.
196+
"""
58197
return 2 * xp.pi * x * self.convergence_func(grid_radius=aa.ArrayIrregular(x))
59198

60199
@property
61200
def ellipticity_rescale(self):
201+
"""
202+
A rescaling factor applied to account for the ellipticity of the mass profile when computing the
203+
Einstein radius from the average convergence equals unity criterion.
204+
205+
For a spherical profile this is 1.0. Elliptical profiles return a factor that maps the elliptical
206+
enclosed mass to an equivalent circular Einstein radius.
207+
"""
62208
return NotImplementedError()
63209

64210
def mass_angular_within_circle_from(self, radius: float):

0 commit comments

Comments
 (0)