Skip to content

Commit 37e41bb

Browse files
Jammy2211claude
authored andcommitted
Merge main into feature/rst-to-myst-md (port Colab tag bumps to .md)
Catches the branch up to main. The 2026.5.1.1 and 2026.5.1.4 release commits bumped the Colab URL tag in several .rst files on main; those files were deleted in the conversion, so the modify/delete conflicts are resolved by keeping the deletion and porting the bump (2026.4.13.6 → 2026.5.1.4) to the MyST .md siblings: - docs/index.md - docs/howtolens/chapter_1_introduction.md - docs/howtolens/chapter_2_lens_modeling.md - docs/howtolens/chapter_3_search_chaining.md - docs/howtolens/chapter_4_pixelizations.md - docs/howtolens/chapter_optional.md - docs/overview/overview_2_new_user_guide.md Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2 parents d820cfc + 79bb150 commit 37e41bb

23 files changed

Lines changed: 557 additions & 162 deletions

README.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ PyAutoLens-JAX: Open-Source Strong Lensing
55
:trim:
66

77
.. |colab| image:: https://colab.research.google.com/assets/colab-badge.svg
8-
:target: https://colab.research.google.com/github/PyAutoLabs/autolens_workspace/blob/2026.4.13.6/start_here.ipynb
8+
:target: https://colab.research.google.com/github/PyAutoLabs/autolens_workspace/blob/2026.5.1.4/start_here.ipynb
99

1010
.. |RTD| image:: https://readthedocs.org/projects/pyautolens/badge/?version=latest
1111
:target: https://pyautolens.readthedocs.io/en/latest/?badge=latest
@@ -46,7 +46,7 @@ PyAutoLens-JAX: Open-Source Strong Lensing
4646

4747
`Installation Guide <https://pyautolens.readthedocs.io/en/latest/installation/overview.html>`_ |
4848
`readthedocs <https://pyautolens.readthedocs.io/en/latest/index.html>`_ |
49-
`Introduction on Colab <https://colab.research.google.com/github/PyAutoLabs/autolens_workspace/blob/2026.4.13.6/start_here.ipynb>`_ |
49+
`Introduction on Colab <https://colab.research.google.com/github/PyAutoLabs/autolens_workspace/blob/2026.5.1.4/start_here.ipynb>`_ |
5050
`HowToLens <https://pyautolens.readthedocs.io/en/latest/howtolens/howtolens.html>`_
5151

5252
.. image:: https://github.com/Jammy2211/PyAutoLogo/blob/main/gifs/pyautolens.gif?raw=true
@@ -63,7 +63,7 @@ The following links are useful for new starters:
6363

6464
- `The PyAutoLens readthedocs <https://pyautolens.readthedocs.io/en/latest>`_: which includes `an overview of PyAutoLens's core features <https://pyautolens.readthedocs.io/en/latest/overview/overview_1_start_here.html>`_, `a new user starting guide <https://pyautolens.readthedocs.io/en/latest/overview/overview_2_new_user_guide.html>`_ and `an installation guide <https://pyautolens.readthedocs.io/en/latest/installation/overview.html>`_.
6565

66-
- `The introduction Jupyter Notebook on Google Colab <https://colab.research.google.com/github/PyAutoLabs/autolens_workspace/blob/2026.4.13.6/start_here.ipynb>`_: try **PyAutoLens** in a web browser (without installation).
66+
- `The introduction Jupyter Notebook on Google Colab <https://colab.research.google.com/github/PyAutoLabs/autolens_workspace/blob/2026.5.1.4/start_here.ipynb>`_: try **PyAutoLens** in a web browser (without installation).
6767

6868
- `The autolens_workspace GitHub repository <https://github.com/PyAutoLabs/autolens_workspace>`_: example scripts covering every **PyAutoLens** use case.
6969

autolens/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@
117117
from .point.solver.shape_solver import ShapeSolver
118118
from .quantity.fit_quantity import FitQuantity
119119
from .quantity.model.analysis import AnalysisQuantity
120+
from .weak.dataset import WeakDataset
121+
from .weak.simulator import SimulatorShearYX
120122

121123
from . import exc
122124
from . import mock as m
@@ -131,7 +133,7 @@
131133

132134
conf.instance.register(__file__)
133135

134-
__version__ = "2026.4.13.6"
136+
__version__ = "2026.5.1.4"
135137

136138
from autoconf import check_version
137139

autolens/analysis/analysis/lens.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,13 @@ def __init__(
6161
self.cosmology = cosmology or Planck15()
6262
self.positions_likelihood_list = positions_likelihood_list
6363

64+
# Mirror the autofit Analysis fallback: if jax isn't installed,
65+
# downgrade silently here too (the parent Analysis.__init__ already
66+
# emitted the loud banner — no need to repeat it).
67+
import importlib.util
68+
if use_jax and importlib.util.find_spec("jax") is None:
69+
use_jax = False
70+
6471
self._use_jax = use_jax
6572

6673
@property

autolens/analysis/result.py

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -314,12 +314,33 @@ def positions_likelihood_from(
314314
Outside test mode this branch is never taken — bad positions still surface as
315315
the original error, so production fits are not silently masked.
316316
317+
Skip-checks safeguard (``PYAUTO_SKIP_CHECKS``): when validation checks are
318+
skipped *and* test mode is active, we return a ``PositionsLH`` built from the
319+
same synthetic ``[(1.0, 0.0), (-1.0, 0.0)]`` pair (with threshold set to
320+
``minimum_threshold`` or 0.5 if unset). This avoids the expensive
321+
multiple-image-position computation while still giving callers a usable
322+
``PositionsLH``, so workspace scripts that chain ``.positions`` continue to
323+
work end-to-end. With skip-checks alone (no test mode) the function still
324+
returns ``None`` — the production opt-out behaviour is unchanged.
325+
317326
Returns
318327
-------
319328
The `PositionsLH` object used to apply a likelihood penalty or resample the positions.
320329
"""
321330

322331
if skip_checks():
332+
if is_test_mode():
333+
synthetic_positions = aa.Grid2DIrregular(
334+
values=[(1.0, 0.0), (-1.0, 0.0)]
335+
)
336+
synthetic_threshold = (
337+
minimum_threshold if minimum_threshold is not None else 0.5
338+
)
339+
return PositionsLH(
340+
positions=synthetic_positions,
341+
threshold=synthetic_threshold,
342+
plane_redshift=plane_redshift,
343+
)
323344
return
324345

325346
positions = (
@@ -420,12 +441,16 @@ def source_plane_inversion_centre_from(
420441
redshift=plane_redshift
421442
)
422443
)
444+
# plane_indexes_with_pixelizations is a list of plane indices that
445+
# have a pixelization, in mapper order. The mapper index for a given
446+
# plane is the position of that plane index within the list, not the
447+
# element at [plane_index].
423448
mapper_index = (
424-
self.max_log_likelihood_tracer.plane_indexes_with_pixelizations[
449+
self.max_log_likelihood_tracer.plane_indexes_with_pixelizations.index(
425450
plane_index
426-
]
451+
)
427452
)
428-
except TypeError:
453+
except (TypeError, ValueError):
429454
mapper_index = 0
430455

431456
inversion = self.max_log_likelihood_fit.inversion

autolens/weak/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from autolens.weak.dataset import WeakDataset
2+
from autolens.weak.simulator import SimulatorShearYX

autolens/weak/dataset.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
"""
2+
Data structure for weak gravitational lensing observations.
3+
4+
Weak lensing measures the small, statistical distortion of background source-galaxy shapes induced by foreground
5+
mass. The observable is a *shear catalogue*: a set of complex shear components ``(gamma_2, gamma_1)`` measured at
6+
the (y, x) sky positions of a population of background galaxies, together with a per-galaxy noise estimate
7+
(typically dominated by intrinsic shape noise — each galaxy has a random unlensed ellipticity that adds to its
8+
measured shear).
9+
10+
``WeakDataset`` holds those three quantities together. It is the weak-lensing analogue of
11+
:class:`autolens.point.dataset.PointDataset` and is the input to a :class:`autolens.weak.fit.FitWeak` (added in a
12+
follow-up step).
13+
14+
The shear catalogue is stored as a :class:`autogalaxy.util.shear_field.ShearYX2DIrregular` so the convention is
15+
the same one pinned by ``PyAutoGalaxy`` PR #366: column 0 is :math:`\\gamma_2`, column 1 is :math:`\\gamma_1`,
16+
and the (y, x) galaxy positions are accessible via ``shear_yx.grid``.
17+
"""
18+
from typing import List, Optional, Union
19+
20+
import autoarray as aa
21+
22+
from autogalaxy.util.shear_field import ShearYX2DIrregular
23+
24+
25+
class WeakDataset:
26+
def __init__(
27+
self,
28+
shear_yx: ShearYX2DIrregular,
29+
noise_map: Union[float, aa.ArrayIrregular, List[float]],
30+
name: str = "",
31+
):
32+
"""
33+
A weak-lensing shear catalogue: a ``ShearYX2DIrregular`` shear field plus a per-galaxy noise map.
34+
35+
Parameters
36+
----------
37+
shear_yx
38+
The measured (or simulated) shear at each background source-galaxy position. Shape
39+
``[total_galaxies, 2]`` with column 0 = :math:`\\gamma_2`, column 1 = :math:`\\gamma_1`. The (y, x)
40+
positions of the galaxies are carried by ``shear_yx.grid``.
41+
noise_map
42+
The per-galaxy shear noise standard deviation (one value per galaxy). For weak lensing this is
43+
dominated by intrinsic shape noise, typically in the range 0.2 - 0.4 per shear component. A scalar
44+
broadcasts to a constant noise level across all galaxies.
45+
name
46+
Optional label, mirroring ``PointDataset.name``. Used by downstream fitting code to pair this
47+
dataset with a corresponding model component when multiple datasets are fitted simultaneously.
48+
"""
49+
self.name = name
50+
51+
if not isinstance(shear_yx, ShearYX2DIrregular):
52+
raise TypeError(
53+
"WeakDataset.shear_yx must be a ShearYX2DIrregular instance; "
54+
f"got {type(shear_yx).__name__}."
55+
)
56+
57+
self.shear_yx = shear_yx
58+
59+
n_galaxies = len(shear_yx)
60+
61+
if isinstance(noise_map, (float, int)):
62+
noise_map = [float(noise_map)] * n_galaxies
63+
64+
if not isinstance(noise_map, aa.ArrayIrregular):
65+
noise_map = aa.ArrayIrregular(values=list(noise_map))
66+
67+
if len(noise_map) != n_galaxies:
68+
raise ValueError(
69+
f"WeakDataset.noise_map has length {len(noise_map)} but shear_yx has "
70+
f"{n_galaxies} entries; the two must match."
71+
)
72+
73+
self.noise_map = noise_map
74+
75+
@property
76+
def positions(self) -> aa.Grid2DIrregular:
77+
"""The (y, x) sky positions of the source galaxies the shear is measured at."""
78+
return self.shear_yx.grid
79+
80+
@property
81+
def n_galaxies(self) -> int:
82+
"""Number of source galaxies in the catalogue."""
83+
return len(self.shear_yx)
84+
85+
@property
86+
def info(self) -> str:
87+
"""A short human-readable summary of the dataset, mirroring ``PointDataset.info``."""
88+
return (
89+
f"name : {self.name}\n"
90+
f"n_galaxies : {self.n_galaxies}\n"
91+
f"shear_yx : {self.shear_yx}\n"
92+
f"noise_map : {self.noise_map}\n"
93+
)
94+
95+
def extent_from(self, buffer: float = 0.1) -> List[float]:
96+
"""The axis-aligned bounding box of the source-galaxy positions, padded by ``buffer`` on each side."""
97+
positions = self.positions
98+
y_max = max(positions[:, 0]) + buffer
99+
y_min = min(positions[:, 0]) - buffer
100+
x_max = max(positions[:, 1]) + buffer
101+
x_min = min(positions[:, 1]) - buffer
102+
return [y_min, y_max, x_min, x_max]

autolens/weak/simulator.py

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
"""
2+
Simulate weak-lensing shear catalogues from a ``Tracer``.
3+
4+
The shear field of a strong lens system is computed by ``Tracer.shear_yx_2d_via_hessian_from``, which differentiates
5+
the deflection-angle field to derive the lensing Hessian and from there the (gamma_2, gamma_1) shear at any (y, x).
6+
That gives a *noise-free* shear field; this module adds two extras needed for a realistic weak-lensing simulation:
7+
8+
1. **Shape noise.** Each background source galaxy has a random unlensed ellipticity, drawn here as iid Gaussian
9+
noise per shear component with standard deviation ``noise_sigma``. Realistic values are around 0.2 - 0.4.
10+
11+
2. **Source-galaxy positions.** Either provide an explicit grid of (y, x) source positions, or let the simulator
12+
draw a uniform-random distribution of ``n_galaxies`` positions inside a square arc-second extent — sufficient
13+
for development work; future iterations may swap in number-density / luminosity-function-driven distributions.
14+
15+
The output is a :class:`autolens.weak.dataset.WeakDataset`.
16+
"""
17+
from typing import Optional
18+
19+
import numpy as np
20+
21+
import autoarray as aa
22+
23+
from autogalaxy.operate.lens_calc import LensCalc
24+
from autogalaxy.util.shear_field import ShearYX2DIrregular
25+
26+
from autolens.weak.dataset import WeakDataset
27+
28+
29+
class SimulatorShearYX:
30+
def __init__(self, noise_sigma: float = 0.3, seed: Optional[int] = None):
31+
"""
32+
Simulator for weak-lensing shear catalogues.
33+
34+
Parameters
35+
----------
36+
noise_sigma
37+
Standard deviation of the per-component Gaussian shape noise added to each measured shear vector.
38+
Values around 0.2 - 0.4 are realistic for ground-based weak-lensing surveys; ``0.0`` produces a
39+
noise-free shear field, useful for unit tests.
40+
seed
41+
Optional seed for the random number generator. When set, both the noise and the random-position
42+
draws (for ``via_tracer_random_positions_from``) are reproducible.
43+
"""
44+
self.noise_sigma = float(noise_sigma)
45+
self.seed = seed
46+
self._rng = np.random.default_rng(seed)
47+
48+
def via_tracer_from(
49+
self,
50+
tracer,
51+
grid: aa.Grid2DIrregular,
52+
name: str = "",
53+
) -> WeakDataset:
54+
"""
55+
Simulate a weak-lensing shear catalogue from a ``Tracer`` evaluated at the given (y, x) source positions.
56+
57+
Computes ``tracer.shear_yx_2d_via_hessian_from(grid=grid)`` — which goes through ``LensCalc`` under the
58+
hood — and adds iid Gaussian shape noise per component with std ``self.noise_sigma``.
59+
60+
Parameters
61+
----------
62+
tracer
63+
A PyAutoLens ``Tracer`` object exposing ``shear_yx_2d_via_hessian_from``.
64+
grid
65+
The (y, x) source-galaxy positions where the shear is measured. Must be an ``aa.Grid2DIrregular``
66+
(or convertible to one).
67+
name
68+
Optional label passed through to ``WeakDataset.name``.
69+
"""
70+
if not isinstance(grid, aa.Grid2DIrregular):
71+
grid = aa.Grid2DIrregular(values=grid)
72+
73+
true_shear = self._true_shear_yx_from(tracer=tracer, grid=grid)
74+
75+
if self.noise_sigma > 0.0:
76+
noise = self._rng.normal(
77+
loc=0.0, scale=self.noise_sigma, size=true_shear.shape
78+
)
79+
shear_array = np.asarray(true_shear) + noise
80+
else:
81+
shear_array = np.asarray(true_shear)
82+
83+
shear_yx = ShearYX2DIrregular(values=shear_array, grid=grid)
84+
85+
noise_map = aa.ArrayIrregular(
86+
values=[self.noise_sigma] * len(grid)
87+
)
88+
89+
return WeakDataset(shear_yx=shear_yx, noise_map=noise_map, name=name)
90+
91+
def via_tracer_random_positions_from(
92+
self,
93+
tracer,
94+
n_galaxies: int,
95+
grid_extent: float = 3.0,
96+
name: str = "",
97+
) -> WeakDataset:
98+
"""
99+
Simulate a weak-lensing shear catalogue at ``n_galaxies`` uniform-random source positions.
100+
101+
Galaxy positions are drawn uniformly inside a square ``[-grid_extent, +grid_extent]`` arc-second box.
102+
This is the simplest sensible distribution for development purposes; production weak-lensing simulations
103+
typically use a survey-specific number density / redshift distribution.
104+
105+
Parameters
106+
----------
107+
tracer
108+
A PyAutoLens ``Tracer`` object.
109+
n_galaxies
110+
Number of background source galaxies to simulate.
111+
grid_extent
112+
Half-width of the square (in arc-seconds) inside which positions are drawn.
113+
name
114+
Optional label passed through to ``WeakDataset.name``.
115+
"""
116+
positions = self._rng.uniform(
117+
low=-grid_extent, high=grid_extent, size=(n_galaxies, 2)
118+
)
119+
grid = aa.Grid2DIrregular(values=positions)
120+
return self.via_tracer_from(tracer=tracer, grid=grid, name=name)
121+
122+
@staticmethod
123+
def _true_shear_yx_from(tracer, grid: aa.Grid2DIrregular):
124+
"""
125+
Evaluate the noise-free shear field of ``tracer`` on ``grid``.
126+
127+
- If the input exposes ``shear_yx_2d_via_hessian_from`` directly, use it.
128+
- Else, if it exposes ``deflections_between_planes_from`` (it duck-types as a ``Tracer``), build a
129+
multi-plane ``LensCalc`` via ``from_tracer``.
130+
- Otherwise treat the input as a mass-like object (single ``MassProfile``, ``Galaxy``, ``Galaxies``)
131+
and build a single-plane ``LensCalc`` via ``from_mass_obj``. This fallback keeps the simulator
132+
usable in unit tests that don't want to spin up a full ``Tracer``.
133+
"""
134+
method = getattr(tracer, "shear_yx_2d_via_hessian_from", None)
135+
if method is not None:
136+
return method(grid=grid)
137+
if hasattr(tracer, "deflections_between_planes_from"):
138+
return LensCalc.from_tracer(tracer).shear_yx_2d_via_hessian_from(grid=grid)
139+
return LensCalc.from_mass_obj(tracer).shear_yx_2d_via_hessian_from(grid=grid)

0 commit comments

Comments
 (0)