From c7dc5c78441da60245602809797f54f067e5348d Mon Sep 17 00:00:00 2001 From: Jammy2211 Date: Thu, 9 Jul 2026 12:32:10 +0100 Subject: [PATCH] Add combined strong+weak lensing features example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit scripts/weak/features/strong_lensing/{simulator,fit,modeling}.py: one tracer simulated into an Imaging dataset (central arcs, no lens light) and a WeakDataset shear field to 10" (space-like noise 0.1, 400 galaxies); fit.py shows the joint log likelihood is the per-dataset sum; modeling.py samples it with Nautilus via the AnalysisFactor/FactorGraphModel API with fully shared priors (NumPy graph loop — the weak factor is NumPy). Science framing: weak shear as the large-radius complement to strong lensing around clusters (Niemiec 2020 hybrid-Lenstool) and groups (Oguri 2012 SGAS). Navigator catalogue regenerated in-branch. Step 8 of the weak-lensing series (autolens_workspace#247). Co-Authored-By: Claude Fable 5 --- llms-full.txt | 6 + scripts/weak/features/__init__.py | 0 .../weak/features/strong_lensing/__init__.py | 0 scripts/weak/features/strong_lensing/fit.py | 155 +++++++++++++ .../weak/features/strong_lensing/modeling.py | 219 ++++++++++++++++++ .../weak/features/strong_lensing/simulator.py | 178 ++++++++++++++ workspace_index.json | 53 +++++ 7 files changed, 611 insertions(+) create mode 100644 scripts/weak/features/__init__.py create mode 100644 scripts/weak/features/strong_lensing/__init__.py create mode 100644 scripts/weak/features/strong_lensing/fit.py create mode 100644 scripts/weak/features/strong_lensing/modeling.py create mode 100644 scripts/weak/features/strong_lensing/simulator.py diff --git a/llms-full.txt b/llms-full.txt index a080e75bf..51e93c93b 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -430,6 +430,12 @@ AUTO-GENERATED by PyAutoBuild — do not edit by hand; regenerate with generate. ## weak +- [Fit: Combined Strong + Weak Lensing](scripts/weak/features/strong_lensing/fit.py): This script fits the combined strong+weak dataset simulated by `simulator.py` in this folder with a single shared `Tracer`: the imaging data via `FitImaging` and the shear catalogue via `FitWeak`. + - Contents: Dataset, Tracer, Imaging Fit, Weak Fit, Joint Likelihood, Shear Profile +- [Modeling: Combined Strong + Weak Lensing](scripts/weak/features/strong_lensing/modeling.py): This script fits the combined dataset of `simulator.py` — an `Imaging` dataset of strongly lensed arcs and a `WeakDataset` of the surrounding shear field — with a **single lens mass model**, using PyAutoFit's factor-graph API to sample the joint likelihood with one non-linear search. + - Contents: Dataset, Model, Analysis List, Analysis Factor & Factor Graph, Search & Model-Fit, Result +- [Simulator: Combined Strong + Weak Lensing](scripts/weak/features/strong_lensing/simulator.py): This script simulates the two faces of the same gravitational lens: an `Imaging` dataset of its strongly lensed arcs, and a `WeakDataset` of the weak shear it imprints on background galaxies at larger radii. Both are generated from **one** `Tracer`, so the datasets share a single true mass distribution — the setup the fit and modeling examples in this folder use to demonstrate joint strong+weak constraints. + - Contents: Dataset Paths, Ray Tracing, Imaging Simulation, Weak Simulation, Output - [Fit: Weak Lensing](scripts/weak/fit.py): This script shows how to fit a strong-lens mass model to a weak gravitational lensing shear catalogue. Where the `imaging` and `interferometer` workflows fit a 2D image of a lensed source, the weak-lensing workflow fits a set of (gamma_2, gamma_1) shear measurements at the (y, x) positions of background source galaxies — a ``WeakDataset`` produced by the simulator script in `scripts/weak/simulator.py`. - Contents: Dataset, Model, Fit, Visualization, Shear Profile, Notes - [__Log Likelihood Function: Weak Lensing__](scripts/weak/likelihood_function.py): This script provides a step-by-step guide of the **PyAutoLens** likelihood function for fitting a lens mass model to a weak gravitational lensing shear catalogue (a `WeakDataset`). It is the weak-lensing companion of the guides for the other dataset types (e.g. `scripts/imaging/likelihood_function.py`), following the same style and level of detail. diff --git a/scripts/weak/features/__init__.py b/scripts/weak/features/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/scripts/weak/features/strong_lensing/__init__.py b/scripts/weak/features/strong_lensing/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/scripts/weak/features/strong_lensing/fit.py b/scripts/weak/features/strong_lensing/fit.py new file mode 100644 index 000000000..5db6b7b6c --- /dev/null +++ b/scripts/weak/features/strong_lensing/fit.py @@ -0,0 +1,155 @@ +""" +Fit: Combined Strong + Weak Lensing +=================================== + +This script fits the combined strong+weak dataset simulated by `simulator.py` in this folder with a single +shared `Tracer`: the imaging data via `FitImaging` and the shear catalogue via `FitWeak`. + +Because the two datasets are statistically independent measurements of the same mass distribution, their +joint log likelihood is simply the sum of the two individual log likelihoods — this additivity is all the +joint modeling in `modeling.py` needs, and this script makes it explicit before a non-linear search is +involved. + +__Contents__ + +- **Dataset:** Load both datasets (auto-simulating them if missing) and mask the imaging data. +- **Tracer:** The single mass model shared by both fits. +- **Imaging Fit:** Fit the strong-lensing image. +- **Weak Fit:** Fit the shear catalogue. +- **Joint Likelihood:** The sum that a joint analysis samples. +- **Shear Profile:** Data vs model in the space cluster weak lensing is usually shown in. +""" + +from autoconf import jax_wrapper # Sets JAX environment before other imports + +# from autoconf import setup_notebook; setup_notebook() + +from pathlib import Path + +import autolens as al +import autolens.plot as aplt + +""" +__Dataset__ + +Load both halves of the combined dataset from `dataset/weak/strong_lensing/`. + +__Dataset Auto-Simulation__ + +If the dataset does not already exist on your system, it will be created by running the corresponding +simulator script. This ensures that all example scripts can be run without manually simulating data first. +""" +dataset_path = Path("dataset") / "weak" / "strong_lensing" + +if al.util.dataset.should_simulate(str(dataset_path)): + import subprocess + import sys + + subprocess.run( + [sys.executable, "scripts/weak/features/strong_lensing/simulator.py"], + check=True, + ) + +dataset_imaging = al.Imaging.from_fits( + data_path=dataset_path / "data.fits", + psf_path=dataset_path / "psf.fits", + noise_map_path=dataset_path / "noise_map.fits", + pixel_scales=0.1, +) + +dataset_weak = al.from_json(file_path=dataset_path / "dataset.json") + +""" +The imaging data is masked with the standard 3.0" circular mask — inside it live the arcs; the shear +catalogue carries the information outside it, to 10". +""" +mask = al.Mask2D.circular( + shape_native=dataset_imaging.shape_native, + pixel_scales=dataset_imaging.pixel_scales, + radius=3.0, +) + +dataset_imaging = dataset_imaging.apply_mask(mask=mask) + +""" +__Tracer__ + +One tracer fits both datasets. We use the simulator's true parameters, so both fits below are "perfect" +up to noise — swap any parameter to see both likelihoods respond, which is exactly what a non-linear +search exploits in `modeling.py`. +""" +lens_galaxy = al.Galaxy( + redshift=0.5, + mass=al.mp.Isothermal( + centre=(0.0, 0.0), + einstein_radius=1.6, + ell_comps=al.convert.ell_comps_from(axis_ratio=0.8, angle=45.0), + ), +) + +source_galaxy = al.Galaxy( + redshift=1.0, + bulge=al.lp.SersicCore( + centre=(0.05, 0.05), + ell_comps=al.convert.ell_comps_from(axis_ratio=0.9, angle=60.0), + intensity=4.0, + effective_radius=0.1, + sersic_index=1.0, + ), +) + +tracer = al.Tracer(galaxies=[lens_galaxy, source_galaxy]) + +""" +__Imaging Fit__ + +The strong-lensing side is the standard `FitImaging` of every imaging example: model image, PSF convolution, +residuals and a Gaussian likelihood over the masked pixels. +""" +fit_imaging = al.FitImaging(dataset=dataset_imaging, tracer=tracer) + +print(f"imaging log_likelihood : {fit_imaging.log_likelihood:.2f}") + +aplt.subplot_fit_imaging(fit=fit_imaging, output_path=dataset_path, output_format="png") + +""" +__Weak Fit__ + +The weak-lensing side is the `FitWeak` of `scripts/weak/fit.py`: the tracer's shear field evaluated at the +catalogue positions, compared to the measured shears over 2N independent components. +""" +fit_weak = al.FitWeak(dataset=dataset_weak, tracer=tracer) + +print(f"weak log_likelihood : {fit_weak.log_likelihood:.2f}") +print( + f"weak chi_squared : {fit_weak.chi_squared:.1f} " + f"(expected ~{2 * dataset_weak.n_galaxies} for the true model)" +) + +aplt.subplot_fit_weak(fit=fit_weak, output_path=dataset_path, output_format="png") + +""" +__Joint Likelihood__ + +The datasets are independent (different galaxies, different noise), so the joint log likelihood of the +shared tracer is their sum. This one line is the entire statistical content of "combining strong and weak +lensing" — `modeling.py` wires it into PyAutoFit's factor-graph API so a non-linear search samples it. +""" +log_likelihood_joint = fit_imaging.log_likelihood + fit_weak.log_likelihood + +print(f"joint log_likelihood : {log_likelihood_joint:.2f}") + +""" +__Shear Profile__ + +The tangential shear profile shows where the weak information lives: the model curve (from the same tracer +fitting the arcs at 1.6") is tested by the binned data points out to 10" — radii the imaging mask never +sees. The cross component scattering around zero is the standard B-mode systematics null test. +""" +aplt.plot_shear_profile( + fit_weak, + centre=(0.0, 0.0), + bins=8, + output_path=dataset_path, + output_format="png", +) diff --git a/scripts/weak/features/strong_lensing/modeling.py b/scripts/weak/features/strong_lensing/modeling.py new file mode 100644 index 000000000..e13f289f5 --- /dev/null +++ b/scripts/weak/features/strong_lensing/modeling.py @@ -0,0 +1,219 @@ +""" +Modeling: Combined Strong + Weak Lensing +======================================== + +This script fits the combined dataset of `simulator.py` — an `Imaging` dataset of strongly lensed arcs and a +`WeakDataset` of the surrounding shear field — with a **single lens mass model**, using PyAutoFit's +factor-graph API to sample the joint likelihood with one non-linear search. + +__Why combine them?__ + +The two datasets constrain the same mass distribution in complementary regimes: + + - **Strong lensing** (the arcs) pins the Einstein radius, and the mass centre exquisitely — but only + *inside* ~1 Einstein radius, and mass-model families that agree there can diverge immediately outside it. + + - **Weak lensing** (the shear catalogue) measures the mass profile and its ellipticity out to many Einstein + radii — noisily per galaxy, but with statistical power in the ensemble, and precisely where the strong + lensing has none. + +Fitting them jointly forces one parametric mass model to satisfy both, the approach of hybrid-Lenstool's +joint strong+weak cluster reconstructions (Niemiec et al. 2020, who showed sequential fitting biases the +profile at 2-3 sigma where a joint fit stays within ~1 sigma) and of the stacked strong+weak analysis of the +Sloan Giant Arcs Survey group-to-cluster lenses (Oguri et al. 2012). + +__Contents__ + +- **Dataset:** Load both datasets (auto-simulating if missing) and mask the imaging data. +- **Model:** One lens model whose priors are shared by both datasets' analyses. +- **Analysis List:** An `AnalysisImaging` and an `AnalysisWeak`, one per dataset. +- **Analysis Factor & Factor Graph:** Combine them so one search samples the joint likelihood. +- **Search & Model-Fit:** Nautilus over the shared parameter space. +- **Result:** The joint constraints, and how to read the strong/weak complementarity in them. +""" + +from autoconf import jax_wrapper # Sets JAX environment before other imports + +# from autoconf import setup_notebook; setup_notebook() + +from pathlib import Path + +import autofit as af +import autolens as al +import autolens.plot as aplt + +""" +__Dataset__ + +Load both halves of the combined dataset, auto-simulating them if missing (the standard pattern of all +example scripts), and apply the standard 3.0" circular mask to the imaging data. Everything outside that +mask is the weak catalogue's territory. +""" +dataset_name = "strong_lensing" +dataset_path = Path("dataset") / "weak" / dataset_name + +if al.util.dataset.should_simulate(str(dataset_path)): + import subprocess + import sys + + subprocess.run( + [sys.executable, "scripts/weak/features/strong_lensing/simulator.py"], + check=True, + ) + +dataset_imaging = al.Imaging.from_fits( + data_path=dataset_path / "data.fits", + psf_path=dataset_path / "psf.fits", + noise_map_path=dataset_path / "noise_map.fits", + pixel_scales=0.1, +) + +mask = al.Mask2D.circular( + shape_native=dataset_imaging.shape_native, + pixel_scales=dataset_imaging.pixel_scales, + radius=3.0, +) + +dataset_imaging = dataset_imaging.apply_mask(mask=mask) + +dataset_weak = al.from_json(file_path=dataset_path / "dataset.json") + +""" +__Model__ + +One model serves both datasets: + + - The lens galaxy's total mass distribution is an `Isothermal` [5 parameters] — the component both + datasets constrain, through the arcs inside the mask and the shear outside it. + + - The source galaxy's light is a linear `SersicCore` [6 parameters] — only the imaging dataset sees this; + the weak catalogue's galaxies are pure shear probes with no model components. + +Crucially the model is composed **once**: passing the same model to both analysis factors below means they +share the same priors and therefore the same parameters — the definition of a joint fit. +""" +# Lens: + +mass = af.Model(al.mp.Isothermal) + +lens = af.Model(al.Galaxy, redshift=0.5, mass=mass) + +# Source: + +bulge = af.Model(al.lp_linear.SersicCore) + +source = af.Model(al.Galaxy, redshift=1.0, bulge=bulge) + +# Overall Lens Model: + +model = af.Collection(galaxies=af.Collection(lens=lens, source=source)) + +print(model.info) + +""" +__Analysis List__ + +One analysis object per dataset, exactly as each would be built in its own modeling script. + +The imaging analysis runs in NumPy mode here (`use_jax=False`): the factor graph below evaluates its factors +in a plain Python loop because the weak-lensing analysis is a NumPy calculation, and mixing an eagerly-JAX +imaging likelihood into that loop gains nothing over NumPy without JIT compilation. On this small masked +dataset the NumPy likelihood is fast, and the weak likelihood is fractions of a millisecond. +""" +analysis_imaging = al.AnalysisImaging(dataset=dataset_imaging, use_jax=False) + +analysis_weak = al.AnalysisWeak(dataset=dataset_weak) + +""" +__Analysis Factor__ + +Each analysis is wrapped in an `AnalysisFactor` paired with the model. Because both factors receive the +*same* model object, the factor graph recognises every prior as shared — a single 11-dimensional parameter +space whose likelihood is the sum of the two factors. + +(In the `multi` examples each factor gets a slightly different copy of the model, e.g. per-wavelength +ellipticities; here total sharing is exactly what "one mass distribution, two datasets" means.) +""" +analysis_factor_imaging = af.AnalysisFactor(prior_model=model, analysis=analysis_imaging) + +analysis_factor_weak = af.AnalysisFactor(prior_model=model, analysis=analysis_weak) + +""" +__Factor Graph__ + +The factors combine into a `FactorGraphModel`, whose `global_prior_model` is the shared parameter space and +whose `log_likelihood_function` is the sum over factors — the quantity `fit.py` computed by hand. +""" +factor_graph = af.FactorGraphModel(analysis_factor_imaging, analysis_factor_weak) + +""" +__Search & Model-Fit__ + +Nautilus samples the joint likelihood. The parameter space is simple (N=11, unimodal), so 100 live points +suffice; expect the fit to take some minutes on an ordinary CPU (the imaging likelihood dominates the cost — +adding the weak factor is essentially free, which is much of weak lensing's practical appeal). +""" +search = af.Nautilus( + path_prefix=Path("weak") / "features", + name="strong_lensing_joint", + unique_tag=dataset_name, + n_live=100, + iterations_per_quick_update=10000, +) + +print( + """ + The joint strong+weak non-linear search has begun running. + + This Jupyter notebook cell will progress once the search has completed - this could take some minutes! + """ +) + +result_list = search.fit(model=factor_graph.global_prior_model, analysis=factor_graph) + +print("The search has finished run - you may now continue the notebook.") + +""" +__Result__ + +The search returns one result per factor (imaging first, weak second), sharing a single posterior. The +mass parameters below are constrained by *both* datasets simultaneously. + +To see the complementarity in play, compare this joint posterior to a run with the weak factor removed +(comment it out of the `FactorGraphModel` above): the Einstein radius barely changes — the arcs own it — +while the constraints on the mass's elliptical components tighten visibly when the shear at large radius is +included, because ellipticity is exactly what coherent tangential shear across the field measures. +""" +result = result_list[0] + +print(result.info) + +print(result.max_log_likelihood_instance) + +""" +The per-factor maximum-likelihood fits visualize each dataset's view of the shared model. +""" +aplt.subplot_fit_imaging( + fit=result_list[0].max_log_likelihood_fit, + output_path=dataset_path, + output_format="png", +) + +aplt.subplot_fit_weak( + fit=result_list[1].max_log_likelihood_fit, + output_path=dataset_path, + output_format="png", +) + +aplt.corner_anesthetic(samples=result.samples) + +""" +__Wrap Up__ + +This example closed the loop the weak-lensing series builds towards: one mass model, constrained inside the +Einstein radius by strong lensing and outside it by weak shear, sampled as a single joint likelihood. + +The same factor-graph pattern extends directly to the realistic versions of this analysis: cluster-scale +lenses with many cluster members (see `scripts/cluster`), real shear catalogues (the upcoming real-data +example in this series), and any other dataset combination (`scripts/multi`). +""" diff --git a/scripts/weak/features/strong_lensing/simulator.py b/scripts/weak/features/strong_lensing/simulator.py new file mode 100644 index 000000000..f2db3c897 --- /dev/null +++ b/scripts/weak/features/strong_lensing/simulator.py @@ -0,0 +1,178 @@ +""" +Simulator: Combined Strong + Weak Lensing +========================================= + +This script simulates the two faces of the same gravitational lens: an `Imaging` dataset of its strongly +lensed arcs, and a `WeakDataset` of the weak shear it imprints on background galaxies at larger radii. +Both are generated from **one** `Tracer`, so the datasets share a single true mass distribution — the setup +the fit and modeling examples in this folder use to demonstrate joint strong+weak constraints. + +__Scientific Context__ + +This combination is how weak lensing is used around strong-lens galaxy clusters and groups (it is *not* the +cosmic-shear or galaxy-galaxy-lensing regime). Strong lensing constrains the mass distribution superbly, but +only inside the Einstein radius where arcs and multiple images form; weak shear extends the constraint to +several times that radius, where most of the halo's mass lives. Joint analyses of this kind include +hybrid-Lenstool's simultaneous strong+weak cluster reconstructions (Niemiec et al. 2020) and the combined +strong and weak lensing analysis of 28 group-to-cluster scale lenses in the Sloan Giant Arcs Survey +(Oguri et al. 2012). + +The scales here are galaxy/group-like and kept small so the examples run quickly: arcs at the Einstein +radius of 1.6", and a shear catalogue extending to 10" — about six Einstein radii, far beyond the 3.0" +region the imaging data constrains. + +__Contents__ + +- **Dataset Paths:** Both datasets are output to a single `dataset/weak/strong_lensing/` folder. +- **Ray Tracing:** The single Tracer both datasets are simulated from. +- **Imaging Simulation:** The strong-lensing image of the lensed source (no lens light, for simplicity). +- **Weak Simulation:** The surrounding shear catalogue, with space-like shape noise. +- **Output:** .fits (imaging), .json (weak catalogue + tracer) and .png visualizations. +""" + +from autoconf import jax_wrapper # Sets JAX environment before other imports + +# from autoconf import setup_notebook; setup_notebook() + +from pathlib import Path + +import autolens as al +import autolens.plot as aplt + +""" +__Dataset Paths__ + +Both datasets describe the same lens, so they live together in one folder: + + - The imaging data will be output to `data.fits` / `noise_map.fits` / `psf.fits`. + - The shear catalogue will be output to `dataset.json`, and the shared truth to `tracer.json`. +""" +dataset_type = "weak" +dataset_name = "strong_lensing" + +dataset_path = Path("dataset") / dataset_type / dataset_name + +""" +__Ray Tracing__ + +The lens is an elliptical `Isothermal` mass distribution (Einstein radius 1.6", axis-ratio 0.8 at 45 degrees) +with no light profile — omitting lens light keeps the imaging side of the example simple, exactly as in the +`imaging/features/no_lens_light` example. The source is a compact cored-Sersic. + +The ellipticity is deliberately pronounced: the outer quadrupole of the mass distribution is the quantity the +weak shear constrains best, so it is where the joint fit visibly improves on imaging alone. +""" +lens_galaxy = al.Galaxy( + redshift=0.5, + mass=al.mp.Isothermal( + centre=(0.0, 0.0), + einstein_radius=1.6, + ell_comps=al.convert.ell_comps_from(axis_ratio=0.8, angle=45.0), + ), +) + +source_galaxy = al.Galaxy( + redshift=1.0, + bulge=al.lp.SersicCore( + centre=(0.05, 0.05), + ell_comps=al.convert.ell_comps_from(axis_ratio=0.9, angle=60.0), + intensity=4.0, + effective_radius=0.1, + sersic_index=1.0, + ), +) + +tracer = al.Tracer(galaxies=[lens_galaxy, source_galaxy]) + +""" +__Imaging Simulation__ + +The strong-lensing image is simulated exactly as in `scripts/imaging/simulator.py`: a 100 x 100 grid at +0.1"/pixel (a 10" field of view whose central ~3" contains the arcs), a Gaussian PSF, and Poisson + sky noise. +""" +grid = al.Grid2D.uniform( + shape_native=(100, 100), + pixel_scales=0.1, +) + +psf = al.Convolver.from_gaussian( + convolve_over_sample_size=1, + shape_native=(11, 11), + sigma=0.1, + pixel_scales=grid.pixel_scales, +) + +simulator = al.SimulatorImaging( + exposure_time=300.0, + psf=psf, + background_sky_level=0.1, + add_poisson_noise_to_data=True, +) + +dataset_imaging = simulator.via_tracer_from(tracer=tracer, grid=grid) + +""" +__Weak Simulation__ + +The weak shear catalogue is simulated from the *same tracer* at 400 uniform-random background-galaxy +positions inside a 10" half-width square. At the Einstein radius the shear is of order unity, but by 10" it +has fallen to |gamma| ~ 0.08 — each individual galaxy is a noisy probe, and the signal lives in their +ensemble. + +A shape noise of `noise_sigma = 0.1` per component corresponds to deep space-based imaging (ground-based +surveys are nearer 0.3); it gives this small catalogue a total detection significance high enough for the +example's joint fit to visibly tighten the mass model. +""" +simulator_weak = al.SimulatorShearYX(noise_sigma=0.1, seed=1) + +dataset_weak = simulator_weak.via_tracer_random_positions_from( + tracer=tracer, + n_galaxies=400, + grid_extent=10.0, + name=dataset_name, +) + +""" +__Output__ + +The imaging dataset is output as .fits (the standard astronomical format), the weak catalogue and the shared +true tracer as .json. +""" +dataset_path.mkdir(parents=True, exist_ok=True) + +aplt.fits_imaging( + dataset=dataset_imaging, + data_path=dataset_path / "data.fits", + psf_path=dataset_path / "psf.fits", + noise_map_path=dataset_path / "noise_map.fits", + overwrite=True, +) + +al.output_to_json(obj=dataset_weak, file_path=dataset_path / "dataset.json") +al.output_to_json(obj=tracer, file_path=dataset_path / "tracer.json") + +""" +__Visualize__ + +The two datasets side by side make the complementarity obvious: all the imaging information sits inside the +central few arc-seconds, while the shear catalogue's quivers cover a field over six Einstein radii across. +The Kaiser-Squires convergence map of the shear field peaks on the strong lens — the same mass seen two ways. +""" +aplt.subplot_imaging_dataset( + dataset=dataset_imaging, output_path=dataset_path, output_format="png" +) + +aplt.subplot_weak_dataset( + dataset=dataset_weak, output_path=dataset_path, output_format="png" +) + +aplt.plot_convergence_map( + shear_yx=dataset_weak.shear_yx, + shape_native=(30, 30), + smoothing_sigma_pixels=1.0, + output_path=dataset_path, + output_format="png", +) + +print(dataset_weak.info) +print(f"Wrote combined strong+weak dataset to {dataset_path}") diff --git a/workspace_index.json b/workspace_index.json index b414316b9..4703cf92e 100644 --- a/workspace_index.json +++ b/workspace_index.json @@ -5785,6 +5785,59 @@ "summary": "Strong gravitational lenses often have point sources (e.g. quasars) that are being lensed, appearing as two or four distinct point-like images. These lenses are particularly useful for measuring cosmological parameters like the Hubble constant, and for studying the small-scale properties of dark matter.", "title": "Start Here: Imaging" }, + { + "contents": [ + "Dataset", + "Tracer", + "Imaging Fit", + "Weak Fit", + "Joint Likelihood", + "Shear Profile" + ], + "cross_refs": [ + "modeling.py", + "scripts/weak/fit.py", + "simulator.py" + ], + "notebook": null, + "path": "scripts/weak/features/strong_lensing/fit.py", + "summary": "This script fits the combined strong+weak dataset simulated by `simulator.py` in this folder with a single shared `Tracer`: the imaging data via `FitImaging` and the shear catalogue via `FitWeak`.", + "title": "Fit: Combined Strong + Weak Lensing" + }, + { + "contents": [ + "Dataset", + "Model", + "Analysis List", + "Analysis Factor & Factor Graph", + "Search & Model-Fit", + "Result" + ], + "cross_refs": [ + "fit.py", + "simulator.py" + ], + "notebook": null, + "path": "scripts/weak/features/strong_lensing/modeling.py", + "summary": "This script fits the combined dataset of `simulator.py` \u2014 an `Imaging` dataset of strongly lensed arcs and a `WeakDataset` of the surrounding shear field \u2014 with a **single lens mass model**, using PyAutoFit's factor-graph API to sample the joint likelihood with one non-linear search.", + "title": "Modeling: Combined Strong + Weak Lensing" + }, + { + "contents": [ + "Dataset Paths", + "Ray Tracing", + "Imaging Simulation", + "Weak Simulation", + "Output" + ], + "cross_refs": [ + "scripts/imaging/simulator.py" + ], + "notebook": null, + "path": "scripts/weak/features/strong_lensing/simulator.py", + "summary": "This script simulates the two faces of the same gravitational lens: an `Imaging` dataset of its strongly lensed arcs, and a `WeakDataset` of the weak shear it imprints on background galaxies at larger radii. Both are generated from **one** `Tracer`, so the datasets share a single true mass distribution \u2014 the setup the fit and modeling examples in this folder use to demonstrate joint strong+weak constraints.", + "title": "Simulator: Combined Strong + Weak Lensing" + }, { "contents": [ "Dataset",