Skip to content

Commit e68af9f

Browse files
Jammy2211claude
authored andcommitted
fix: latent_variables guide reuses quick fit + draws from PDF (PyAutoLabs/autolens_workspace#230)
Consistency change mirroring the autolens_workspace fix. The guide ran a bespoke Nautilus fit and computed latents over the full posterior; now it reuses the shared `_quick_fit.py` result via the aggregator and computes latents over 20 PDF draws (`samples.samples_drawn_randomly_via_pdf_from`). Adds a "Controlling the Cost via Config" docstring (latent.yaml = which latents, output.yaml latent_draw_via_pdf = how many draws). Cold run 63s. Notebook regenerated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XwGFQNneASyEoHJm3RGecm
1 parent 3fcfc07 commit e68af9f

4 files changed

Lines changed: 116 additions & 80 deletions

File tree

llms-full.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ AUTO-GENERATED by PyAutoBuild — do not edit by hand; regenerate with generate.
231231
- [Database: Introduction](scripts/guides/results/database/start_here.py): The default behaviour of model-fitting results output is to be written to hard-disc in folders. These are simple to navigate and manually check.
232232
- Contents: Unique Identifiers, Dataset, Results From Hard Disk, Building a Database File From an Output Folder, Writing Directly To Database, Files, Generators, Model, Search, Samples, Wrap Up
233233
- [Results: Latent Variables](scripts/guides/results/latent_variables.py): A latent variable is a quantity derived from the model parameters that is not itself sampled by the non-linear search — its posterior is induced by the posterior over the parameters. PyAutoGalaxy ships a curated catalogue of galaxy-level latent variables out of the box, controlled by ``autogalaxy/config/latent.yaml``. This tutorial shows what the catalogue contains, how to toggle individual latents on or off via the workspace config, how to load latent results from a completed fit, and how to extend ``AnalysisImaging`` with a user-defined latent when the curated set isn't enough.
234-
- Contents: Galaxy Latents in PyAutoGalaxy: The curated default catalogue from ``autogalaxy/config/latent.yaml``., Toggling Latents: The workspace ``config/latent.yaml`` override and why it shadows the library default., Model Fit: A quick fit that produces real latent output for the loading section below., Loading Latent Results: Reading ``latent/samples.csv`` / ``latent_summary.json`` via, Extending with a Custom Latent: Subclass ``ag.AnalysisImaging``, override ``LATENT_KEYS`` and, Contributing Upstream: When your custom latent is general enough, consider promoting it to the library.
234+
- Contents: Galaxy Latents in PyAutoGalaxy: The curated default catalogue from ``autogalaxy/config/latent.yaml``., Toggling Latents: The workspace ``config/latent.yaml`` override and why it shadows the library default., Model Fit: Reuse the shared quick fit (``_quick_fit.py``) that produces real latent output., Loading Latent Results: ``analysis.compute_latent_samples`` over a subset of PDF draws, and the two, Extending with a Custom Latent: Subclass ``ag.AnalysisImaging``, override ``LATENT_KEYS`` and, Contributing Upstream: When your custom latent is general enough, consider promoting it to the library.
235235
- [Results: Start Here](scripts/guides/results/start_here.py): After a model-fit completes, nearly everything a user could need is written to the `output/` folder. Most of it can be loaded back into full Python objects with a single line of code, via either `.json` files (for model objects like the `Galaxies`, `Model` and samples) or `.fits` files (for imaging products like the model image, residuals and per-galaxy images).
236236
- Contents: Model Fit, Info
237237
- [Results: CSV](scripts/guides/results/workflow/csv_make.py): This example is a results workflow example, which means it provides tool to set up an effective workflow inspecting and interpreting the large libraries of modeling results.

notebooks/guides/results/latent_variables.ipynb

Lines changed: 61 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@
2424
"\n",
2525
" - Galaxy Latents in PyAutoGalaxy: The curated default catalogue from ``autogalaxy/config/latent.yaml``.\n",
2626
" - Toggling Latents: The workspace ``config/latent.yaml`` override and why it shadows the library default.\n",
27-
" - Model Fit: A quick fit that produces real latent output for the loading section below.\n",
28-
" - Loading Latent Results: Reading ``latent/samples.csv`` / ``latent_summary.json`` via\n",
29-
" ``analysis.compute_latent_samples(result.samples)`` and the standard ``Samples`` API.\n",
27+
" - Model Fit: Reuse the shared quick fit (``_quick_fit.py``) that produces real latent output.\n",
28+
" - Loading Latent Results: ``analysis.compute_latent_samples`` over a subset of PDF draws, and the two\n",
29+
" config surfaces (``latent.yaml`` / ``output.yaml``) that control which latents and how many draws.\n",
3030
" - Extending with a Custom Latent: Subclass ``ag.AnalysisImaging``, override ``LATENT_KEYS`` and\n",
3131
" ``compute_latent_variables`` to add your own derived quantity.\n",
3232
" - Contributing Upstream: When your custom latent is general enough, consider promoting it to the library."
@@ -115,14 +115,13 @@
115115
"'''\n",
116116
"__Model Fit__\n",
117117
"\n",
118-
"To make the loading and extending sections below concrete, we run a quick model fit on the standard simple-\n",
119-
"imaging dataset that ships with the workspace. The model is a single galaxy with a Sersic bulge \u2014 keeping it\n",
120-
"small so the example runs in a reasonable time.\n",
118+
"The loading and extending sections below need a completed fit to read latents from. Rather than run a bespoke\n",
119+
"fit here, we reuse the shared quick fit that the other results guides use: ``_quick_fit.py`` writes a capped\n",
120+
"single-galaxy (Sersic bulge + Exponential disk) fit of the standard ``simple`` imaging dataset to\n",
121+
"``output/results_folder/``. It is idempotent \u2014 it returns immediately if those results already exist \u2014 so the\n",
122+
"non-linear search is paid once across the whole guide suite rather than repeated in every example.\n",
121123
"\n",
122-
"We pass ``magzero=25.0`` to ``ag.AnalysisImaging`` so ``total_galaxy_0_flux_mujy`` populates with a real value\n",
123-
"rather than NaN. If you forget it, ``total_galaxy_0_flux_mujy`` will be NaN and the library will log a single\n",
124-
"warning per process noting the conversion was skipped \u2014 the fit itself is unaffected, and the raw\n",
125-
"``total_galaxy_0_flux`` column populates normally.\n",
124+
"We then load that fit's samples via the aggregator, exactly as ``start_here.py`` and ``aggregator/models.py`` do.\n",
126125
"'''"
127126
],
128127
"outputs": [],
@@ -132,17 +131,38 @@
132131
"cell_type": "code",
133132
"metadata": {},
134133
"source": [
135-
"dataset_name = \"simple\"\n",
136-
"dataset_path = Path(\"dataset\") / \"imaging\" / dataset_name\n",
134+
"import subprocess\n",
135+
"import sys\n",
136+
"\n",
137+
"subprocess.run(\n",
138+
" [sys.executable, \"scripts/guides/results/_quick_fit.py\"],\n",
139+
" check=True,\n",
140+
")\n",
137141
"\n",
138-
"if not dataset_path.exists():\n",
139-
" import subprocess\n",
140-
" import sys\n",
142+
"from autofit.aggregator.aggregator import Aggregator\n",
141143
"\n",
142-
" subprocess.run(\n",
143-
" [sys.executable, \"scripts/imaging/simulator.py\"],\n",
144-
" check=True,\n",
145-
" )\n",
144+
"agg = Aggregator.from_directory(directory=Path(\"output\") / \"results_folder\")\n",
145+
"samples = list(agg)[0].samples"
146+
],
147+
"outputs": [],
148+
"execution_count": null
149+
},
150+
{
151+
"cell_type": "markdown",
152+
"metadata": {},
153+
"source": [
154+
"The samples carry the parameter posterior; the ``analysis`` carries the machinery that turns each posterior\n",
155+
"draw into latent values. We rebuild the dataset and an ``ag.AnalysisImaging`` with ``magzero=25.0`` so\n",
156+
"``total_galaxy_0_flux_mujy`` populates with a real value rather than NaN (without it that column is NaN and the\n",
157+
"library logs a single warning per process; the raw ``total_galaxy_0_flux`` column populates normally)."
158+
]
159+
},
160+
{
161+
"cell_type": "code",
162+
"metadata": {},
163+
"source": [
164+
"dataset_name = \"simple\"\n",
165+
"dataset_path = Path(\"dataset\") / \"imaging\" / dataset_name\n",
146166
"\n",
147167
"dataset = ag.Imaging.from_fits(\n",
148168
" data_path=dataset_path / \"data.fits\",\n",
@@ -158,18 +178,7 @@
158178
")\n",
159179
"dataset = dataset.apply_mask(mask=mask)\n",
160180
"\n",
161-
"galaxy_model = af.Model(ag.Galaxy, redshift=0.5, bulge=af.Model(ag.lp.Sersic))\n",
162-
"model = af.Collection(galaxies=af.Collection(galaxy=galaxy_model))\n",
163-
"\n",
164-
"analysis = ag.AnalysisImaging(dataset=dataset, use_jax=False, magzero=25.0)\n",
165-
"\n",
166-
"search = af.Nautilus(\n",
167-
" name=\"cookbook_latent_variables\",\n",
168-
" n_live=50,\n",
169-
" n_like_max=300,\n",
170-
")\n",
171-
"\n",
172-
"result = search.fit(model=model, analysis=analysis)"
181+
"analysis = ag.AnalysisImaging(dataset=dataset, use_jax=False, magzero=25.0)"
173182
],
174183
"outputs": [],
175184
"execution_count": null
@@ -180,21 +189,34 @@
180189
"source": [
181190
"__Loading Latent Results__\n",
182191
"\n",
183-
"The fit above produces ``latent/samples.csv`` and ``latent/latent_summary.json`` under the search's output\n",
184-
"directory. You can read them back into a ``Samples`` object via ``analysis.compute_latent_samples(result.samples)``.\n",
185-
"The returned object exposes the same API as the parameter ``Samples`` \u2014 ``median_pdf``, ``max_log_likelihood``,\n",
186-
"``values_at_sigma_1``, and so on \u2014 but reports on the induced latent posterior rather than the parameter posterior.\n",
192+
"``analysis.compute_latent_samples(samples)`` reads the posterior into a ``Samples`` object that exposes the same\n",
193+
"API as the parameter ``Samples`` \u2014 ``median_pdf``, ``max_log_likelihood``, ``values_at_sigma_1``, and so on \u2014 but\n",
194+
"reports on the induced latent posterior. Because both ``total_galaxy_0_flux`` and ``total_galaxy_0_flux_mujy`` are\n",
195+
"enabled in this workspace, the returned instance exposes both attributes; enabling additional latents in the\n",
196+
"workspace yaml makes them all appear as attributes on the same instance.\n",
197+
"\n",
198+
"__Controlling the Cost via Config__\n",
199+
"\n",
200+
"Latents are computed by reconstructing a fit for every posterior sample, so the cost scales with the number of\n",
201+
"samples. Two workspace config files control this:\n",
202+
"\n",
203+
" - ``config/latent.yaml`` \u2014 controls *which* latents are computed. Disable the ones you don't need.\n",
204+
"\n",
205+
" - ``config/output.yaml`` \u2014 ``latent_draw_via_pdf`` / ``latent_draw_via_pdf_size`` control *how many* posterior\n",
206+
" draws the latents are computed over when a live search updates. Drawing a representative subset from the PDF\n",
207+
" gives faithful latent errors at a fraction of the every-sample cost.\n",
187208
"\n",
188-
"Because both ``total_galaxy_0_flux`` and ``total_galaxy_0_flux_mujy`` are enabled in this workspace, the\n",
189-
"returned instance exposes both attributes. If you enable additional latents in the workspace yaml, they all\n",
190-
"appear as attributes on the same instance."
209+
"Here we mirror that draw-from-PDF behaviour explicitly with ``samples.samples_drawn_randomly_via_pdf_from``,\n",
210+
"computing the latents over 20 PDF draws so this guide runs quickly while still producing a real, representative\n",
211+
"latent posterior. For a publication-quality result, compute over all samples (or a larger number of draws)."
191212
]
192213
},
193214
{
194215
"cell_type": "code",
195216
"metadata": {},
196217
"source": [
197-
"latent_samples = analysis.compute_latent_samples(result.samples)\n",
218+
"latent_draws = samples.samples_drawn_randomly_via_pdf_from(total_draws=20)\n",
219+
"latent_samples = analysis.compute_latent_samples(latent_draws)\n",
198220
"\n",
199221
"median_instance = latent_samples.median_pdf()\n",
200222
"print(f\"Median PDF total_galaxy_0_flux: {median_instance.total_galaxy_0_flux}\")\n",

scripts/guides/results/latent_variables.py

Lines changed: 49 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@
1919
2020
- Galaxy Latents in PyAutoGalaxy: The curated default catalogue from ``autogalaxy/config/latent.yaml``.
2121
- Toggling Latents: The workspace ``config/latent.yaml`` override and why it shadows the library default.
22-
- Model Fit: A quick fit that produces real latent output for the loading section below.
23-
- Loading Latent Results: Reading ``latent/samples.csv`` / ``latent_summary.json`` via
24-
``analysis.compute_latent_samples(result.samples)`` and the standard ``Samples`` API.
22+
- Model Fit: Reuse the shared quick fit (``_quick_fit.py``) that produces real latent output.
23+
- Loading Latent Results: ``analysis.compute_latent_samples`` over a subset of PDF draws, and the two
24+
config surfaces (``latent.yaml`` / ``output.yaml``) that control which latents and how many draws.
2525
- Extending with a Custom Latent: Subclass ``ag.AnalysisImaging``, override ``LATENT_KEYS`` and
2626
``compute_latent_variables`` to add your own derived quantity.
2727
- Contributing Upstream: When your custom latent is general enough, consider promoting it to the library.
@@ -84,26 +84,35 @@
8484
"""
8585
__Model Fit__
8686
87-
To make the loading and extending sections below concrete, we run a quick model fit on the standard simple-
88-
imaging dataset that ships with the workspace. The model is a single galaxy with a Sersic bulge — keeping it
89-
small so the example runs in a reasonable time.
87+
The loading and extending sections below need a completed fit to read latents from. Rather than run a bespoke
88+
fit here, we reuse the shared quick fit that the other results guides use: ``_quick_fit.py`` writes a capped
89+
single-galaxy (Sersic bulge + Exponential disk) fit of the standard ``simple`` imaging dataset to
90+
``output/results_folder/``. It is idempotent — it returns immediately if those results already exist — so the
91+
non-linear search is paid once across the whole guide suite rather than repeated in every example.
9092
91-
We pass ``magzero=25.0`` to ``ag.AnalysisImaging`` so ``total_galaxy_0_flux_mujy`` populates with a real value
92-
rather than NaN. If you forget it, ``total_galaxy_0_flux_mujy`` will be NaN and the library will log a single
93-
warning per process noting the conversion was skipped — the fit itself is unaffected, and the raw
94-
``total_galaxy_0_flux`` column populates normally.
93+
We then load that fit's samples via the aggregator, exactly as ``start_here.py`` and ``aggregator/models.py`` do.
9594
"""
96-
dataset_name = "simple"
97-
dataset_path = Path("dataset") / "imaging" / dataset_name
95+
import subprocess
96+
import sys
97+
98+
subprocess.run(
99+
[sys.executable, "scripts/guides/results/_quick_fit.py"],
100+
check=True,
101+
)
102+
103+
from autofit.aggregator.aggregator import Aggregator
98104

99-
if not dataset_path.exists():
100-
import subprocess
101-
import sys
105+
agg = Aggregator.from_directory(directory=Path("output") / "results_folder")
106+
samples = list(agg)[0].samples
102107

103-
subprocess.run(
104-
[sys.executable, "scripts/imaging/simulator.py"],
105-
check=True,
106-
)
108+
"""
109+
The samples carry the parameter posterior; the ``analysis`` carries the machinery that turns each posterior
110+
draw into latent values. We rebuild the dataset and an ``ag.AnalysisImaging`` with ``magzero=25.0`` so
111+
``total_galaxy_0_flux_mujy`` populates with a real value rather than NaN (without it that column is NaN and the
112+
library logs a single warning per process; the raw ``total_galaxy_0_flux`` column populates normally).
113+
"""
114+
dataset_name = "simple"
115+
dataset_path = Path("dataset") / "imaging" / dataset_name
107116

108117
dataset = ag.Imaging.from_fits(
109118
data_path=dataset_path / "data.fits",
@@ -119,32 +128,34 @@
119128
)
120129
dataset = dataset.apply_mask(mask=mask)
121130

122-
galaxy_model = af.Model(ag.Galaxy, redshift=0.5, bulge=af.Model(ag.lp.Sersic))
123-
model = af.Collection(galaxies=af.Collection(galaxy=galaxy_model))
124-
125131
analysis = ag.AnalysisImaging(dataset=dataset, use_jax=False, magzero=25.0)
126132

127-
search = af.Nautilus(
128-
name="cookbook_latent_variables",
129-
n_live=50,
130-
n_like_max=300,
131-
)
132-
133-
result = search.fit(model=model, analysis=analysis)
134-
135133
"""
136134
__Loading Latent Results__
137135
138-
The fit above produces ``latent/samples.csv`` and ``latent/latent_summary.json`` under the search's output
139-
directory. You can read them back into a ``Samples`` object via ``analysis.compute_latent_samples(result.samples)``.
140-
The returned object exposes the same API as the parameter ``Samples`` — ``median_pdf``, ``max_log_likelihood``,
141-
``values_at_sigma_1``, and so on — but reports on the induced latent posterior rather than the parameter posterior.
136+
``analysis.compute_latent_samples(samples)`` reads the posterior into a ``Samples`` object that exposes the same
137+
API as the parameter ``Samples`` — ``median_pdf``, ``max_log_likelihood``, ``values_at_sigma_1``, and so on — but
138+
reports on the induced latent posterior. Because both ``total_galaxy_0_flux`` and ``total_galaxy_0_flux_mujy`` are
139+
enabled in this workspace, the returned instance exposes both attributes; enabling additional latents in the
140+
workspace yaml makes them all appear as attributes on the same instance.
141+
142+
__Controlling the Cost via Config__
143+
144+
Latents are computed by reconstructing a fit for every posterior sample, so the cost scales with the number of
145+
samples. Two workspace config files control this:
146+
147+
- ``config/latent.yaml`` — controls *which* latents are computed. Disable the ones you don't need.
148+
149+
- ``config/output.yaml`` — ``latent_draw_via_pdf`` / ``latent_draw_via_pdf_size`` control *how many* posterior
150+
draws the latents are computed over when a live search updates. Drawing a representative subset from the PDF
151+
gives faithful latent errors at a fraction of the every-sample cost.
142152
143-
Because both ``total_galaxy_0_flux`` and ``total_galaxy_0_flux_mujy`` are enabled in this workspace, the
144-
returned instance exposes both attributes. If you enable additional latents in the workspace yaml, they all
145-
appear as attributes on the same instance.
153+
Here we mirror that draw-from-PDF behaviour explicitly with ``samples.samples_drawn_randomly_via_pdf_from``,
154+
computing the latents over 20 PDF draws so this guide runs quickly while still producing a real, representative
155+
latent posterior. For a publication-quality result, compute over all samples (or a larger number of draws).
146156
"""
147-
latent_samples = analysis.compute_latent_samples(result.samples)
157+
latent_draws = samples.samples_drawn_randomly_via_pdf_from(total_draws=20)
158+
latent_samples = analysis.compute_latent_samples(latent_draws)
148159

149160
median_instance = latent_samples.median_pdf()
150161
print(f"Median PDF total_galaxy_0_flux: {median_instance.total_galaxy_0_flux}")

workspace_index.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -693,16 +693,19 @@
693693
"contents": [
694694
"Galaxy Latents in PyAutoGalaxy: The curated default catalogue from ``autogalaxy/config/latent.yaml``.",
695695
"Toggling Latents: The workspace ``config/latent.yaml`` override and why it shadows the library default.",
696-
"Model Fit: A quick fit that produces real latent output for the loading section below.",
697-
"Loading Latent Results: Reading ``latent/samples.csv`` / ``latent_summary.json`` via",
696+
"Model Fit: Reuse the shared quick fit (``_quick_fit.py``) that produces real latent output.",
697+
"Loading Latent Results: ``analysis.compute_latent_samples`` over a subset of PDF draws, and the two",
698698
"Extending with a Custom Latent: Subclass ``ag.AnalysisImaging``, override ``LATENT_KEYS`` and",
699699
"Contributing Upstream: When your custom latent is general enough, consider promoting it to the library."
700700
],
701701
"cross_refs": [
702702
"../../../autofit_workspace/scripts/cookbooks/latent_variables.py",
703+
"_quick_fit.py",
704+
"aggregator/models.py",
703705
"autogalaxy/imaging/model/latent.py",
704706
"euclid_strong_lens_modeling_pipeline/util.py",
705707
"scripts/guides/units/flux.py",
708+
"start_here.py",
706709
"test_autogalaxy/imaging/test_latent.py"
707710
],
708711
"notebook": "notebooks/guides/results/latent_variables.ipynb",

0 commit comments

Comments
 (0)