From 62de0068277fd1fe42aba4616de3e862b9ceb5a1 Mon Sep 17 00:00:00 2001 From: Jammy2211 Date: Tue, 14 Jul 2026 14:00:40 +0100 Subject: [PATCH] fix: consolidate guides/results tutorials onto one _quick_fit.py start_here.py, aggregator/galaxies_fits.py and aggregator/samples.py each ran their own capped n_like_max=300 Nautilus fit, but only _quick_fit.py disables samples_weight_threshold. The others pruned to 2 samples on disk, so their deep sample indexing (from_sample_index(-10), parameter_lists[9]) raised IndexError once the release profile ran the real sampler (guides/results/ unsets PYAUTO_TEST_MODE, so is_test_mode() is False and PyAutoFit#1368 does not apply). Order-dependent: start_here.py runs first and plants the 2-sample fit the aggregator readers then choke on. Consolidate onto a single fit source, _quick_fit.py: give it a suffix-free primary unique_tag (simple__no_lens_light) plus one more (_1) for the aggregator's multi-result iteration, and make the three self-fitting scripts' inline model match it (Isothermal + external shear, MGE source). search.fit() then genuinely resumes the 300-sample fit instead of running a divergent shallow one, making the scripts' "resumes from the cached fit" docstrings true. The fit identifier is [search, model, unique_tag] only, so a plain AnalysisImaging inline still resumes _quick_fit's AnalysisLatent fit. samples.py's source changes Sersic -> MGE to match, and its two source.bulge.sersic_index filter examples move to lens.shear.gamma_1. Also fixes queries.py, whose unique_tag == "simple__no_lens_light" query previously matched nothing. Validated: ordered guides/results run under env_vars_release.yaml passes 14/14 (was 2 IndexError failures); both fits retain 300 samples on disk, no 2-sample fit. Notebooks regenerated. Resolves the aggregator portion (cluster E) of the release-validation tail, PyAutoHeart#72. Co-Authored-By: Claude Opus 4.8 --- notebooks/guides/results/_quick_fit.ipynb | 16 ++++++++++-- .../results/aggregator/galaxies_fits.ipynb | 7 ++++- .../guides/results/aggregator/samples.ipynb | 26 ++++++++++++++----- notebooks/guides/results/start_here.ipynb | 7 ++++- scripts/guides/results/_quick_fit.py | 16 ++++++++++-- .../results/aggregator/galaxies_fits.py | 7 ++++- scripts/guides/results/aggregator/samples.py | 26 ++++++++++++++----- scripts/guides/results/start_here.py | 7 ++++- 8 files changed, 90 insertions(+), 22 deletions(-) diff --git a/notebooks/guides/results/_quick_fit.ipynb b/notebooks/guides/results/_quick_fit.ipynb index d111dce90..9f5f9ca33 100644 --- a/notebooks/guides/results/_quick_fit.ipynb +++ b/notebooks/guides/results/_quick_fit.ipynb @@ -185,11 +185,23 @@ "\n", "analysis = AnalysisLatent(dataset=dataset, use_jax=True)\n", "\n", - "for i in range(2):\n", + "# Two fits are written to `results_folder/`:\n", + "#\n", + "# - The first uses the bare `dataset_name` as its `unique_tag`. This is the\n", + "# canonical fit that the single-result tutorials (`start_here.py`,\n", + "# `aggregator/galaxies_fits.py`, `aggregator/samples.py`) resume: they build\n", + "# the *same* model and search below, so `search.fit(...)` returns this\n", + "# completed fit instead of redoing the search.\n", + "# - The second (`_1`) gives the aggregator tutorials a second result to iterate\n", + "# over, demonstrating how the `Aggregator` loads many fits from one folder.\n", + "#\n", + "# Both share the model, search and analysis, so both retain the full samples\n", + "# (`samples_weight_threshold = None` above) needed for indexed sample access.\n", + "for unique_tag in [dataset_name, f\"{dataset_name}_1\"]:\n", " search = af.Nautilus(\n", " path_prefix=Path(\"results_folder\"),\n", " name=\"results\",\n", - " unique_tag=f\"{dataset_name}_{i}\",\n", + " unique_tag=unique_tag,\n", " n_live=100,\n", " n_batch=50,\n", " iterations_per_quick_update=10000,\n", diff --git a/notebooks/guides/results/aggregator/galaxies_fits.ipynb b/notebooks/guides/results/aggregator/galaxies_fits.ipynb index b407ac315..2cb64392a 100644 --- a/notebooks/guides/results/aggregator/galaxies_fits.ipynb +++ b/notebooks/guides/results/aggregator/galaxies_fits.ipynb @@ -215,7 +215,12 @@ "\n", "model = af.Collection(\n", " galaxies=af.Collection(\n", - " lens=af.Model(al.Galaxy, redshift=0.5, mass=al.mp.Isothermal),\n", + " lens=af.Model(\n", + " al.Galaxy,\n", + " redshift=0.5,\n", + " mass=al.mp.Isothermal,\n", + " shear=al.mp.ExternalShear,\n", + " ),\n", " source=af.Model(al.Galaxy, redshift=1.0, bulge=bulge, disk=None),\n", " ),\n", ")\n", diff --git a/notebooks/guides/results/aggregator/samples.ipynb b/notebooks/guides/results/aggregator/samples.ipynb index 0c3e3bec5..f30802c82 100644 --- a/notebooks/guides/results/aggregator/samples.ipynb +++ b/notebooks/guides/results/aggregator/samples.ipynb @@ -193,10 +193,22 @@ "\n", "dataset = dataset.apply_mask(mask=mask)\n", "\n", + "bulge = al.model_util.mge_model_from(\n", + " mask_radius=mask_radius,\n", + " total_gaussians=20,\n", + " gaussian_per_basis=1,\n", + " centre_prior_is_uniform=False,\n", + ")\n", + "\n", "model = af.Collection(\n", " galaxies=af.Collection(\n", - " lens=af.Model(al.Galaxy, redshift=0.5, mass=al.mp.Isothermal),\n", - " source=af.Model(al.Galaxy, redshift=1.0, bulge=al.lp.Sersic),\n", + " lens=af.Model(\n", + " al.Galaxy,\n", + " redshift=0.5,\n", + " mass=al.mp.Isothermal,\n", + " shear=al.mp.ExternalShear,\n", + " ),\n", + " source=af.Model(al.Galaxy, redshift=1.0, bulge=bulge, disk=None),\n", " ),\n", ")\n", "\n", @@ -860,19 +872,19 @@ "samples = samples.with_paths(\n", " [\n", " (\"galaxies\", \"lens\", \"mass\", \"einstein_radius\"),\n", - " (\"galaxies\", \"source\", \"bulge\", \"sersic_index\"),\n", + " (\"galaxies\", \"lens\", \"shear\", \"gamma_1\"),\n", " ]\n", ")\n", "\n", "print(\n", " \"All parameters of the very first sample (containing only the lens mass's einstein radius and \"\n", - " \"source bulge's sersic index).\"\n", + " \"the first component of the lens's external shear).\"\n", ")\n", "print(samples.parameter_lists[0])\n", "\n", "print(\n", " \"Maximum Log Likelihood Model Instances (containing only the lens mass's einstein radius and \"\n", - " \"source bulge's sersic index):\\n\"\n", + " \"the first component of the lens's external shear):\\n\"\n", ")\n", "print(samples.max_log_likelihood(as_instance=False))" ], @@ -898,12 +910,12 @@ "samples = result.samples\n", "\n", "samples = samples.with_paths(\n", - " [\"galaxies.lens.mass.einstein_radius\", \"galaxies.source.bulge.sersic_index\"]\n", + " [\"galaxies.lens.mass.einstein_radius\", \"galaxies.lens.shear.gamma_1\"]\n", ")\n", "\n", "print(\n", " \"All parameters of the very first sample (containing only the lens mass's einstein radius and \"\n", - " \"source bulge's sersic index).\"\n", + " \"the first component of the lens's external shear).\"\n", ")" ], "outputs": [], diff --git a/notebooks/guides/results/start_here.ipynb b/notebooks/guides/results/start_here.ipynb index f91b6baef..780f42b6b 100644 --- a/notebooks/guides/results/start_here.ipynb +++ b/notebooks/guides/results/start_here.ipynb @@ -225,7 +225,12 @@ "\n", "model = af.Collection(\n", " galaxies=af.Collection(\n", - " lens=af.Model(al.Galaxy, redshift=0.5, mass=al.mp.Isothermal),\n", + " lens=af.Model(\n", + " al.Galaxy,\n", + " redshift=0.5,\n", + " mass=al.mp.Isothermal,\n", + " shear=al.mp.ExternalShear,\n", + " ),\n", " source=af.Model(al.Galaxy, redshift=1.0, bulge=bulge, disk=None),\n", " ),\n", ")\n", diff --git a/scripts/guides/results/_quick_fit.py b/scripts/guides/results/_quick_fit.py index dc7f7061a..875090b5c 100644 --- a/scripts/guides/results/_quick_fit.py +++ b/scripts/guides/results/_quick_fit.py @@ -138,11 +138,23 @@ class AnalysisLatent(al.AnalysisImaging): analysis = AnalysisLatent(dataset=dataset, use_jax=True) -for i in range(2): +# Two fits are written to `results_folder/`: +# +# - The first uses the bare `dataset_name` as its `unique_tag`. This is the +# canonical fit that the single-result tutorials (`start_here.py`, +# `aggregator/galaxies_fits.py`, `aggregator/samples.py`) resume: they build +# the *same* model and search below, so `search.fit(...)` returns this +# completed fit instead of redoing the search. +# - The second (`_1`) gives the aggregator tutorials a second result to iterate +# over, demonstrating how the `Aggregator` loads many fits from one folder. +# +# Both share the model, search and analysis, so both retain the full samples +# (`samples_weight_threshold = None` above) needed for indexed sample access. +for unique_tag in [dataset_name, f"{dataset_name}_1"]: search = af.Nautilus( path_prefix=Path("results_folder"), name="results", - unique_tag=f"{dataset_name}_{i}", + unique_tag=unique_tag, n_live=100, n_batch=50, iterations_per_quick_update=10000, diff --git a/scripts/guides/results/aggregator/galaxies_fits.py b/scripts/guides/results/aggregator/galaxies_fits.py index b2723bd00..49f099b9e 100644 --- a/scripts/guides/results/aggregator/galaxies_fits.py +++ b/scripts/guides/results/aggregator/galaxies_fits.py @@ -135,7 +135,12 @@ model = af.Collection( galaxies=af.Collection( - lens=af.Model(al.Galaxy, redshift=0.5, mass=al.mp.Isothermal), + lens=af.Model( + al.Galaxy, + redshift=0.5, + mass=al.mp.Isothermal, + shear=al.mp.ExternalShear, + ), source=af.Model(al.Galaxy, redshift=1.0, bulge=bulge, disk=None), ), ) diff --git a/scripts/guides/results/aggregator/samples.py b/scripts/guides/results/aggregator/samples.py index d118df4cf..44500a652 100644 --- a/scripts/guides/results/aggregator/samples.py +++ b/scripts/guides/results/aggregator/samples.py @@ -113,10 +113,22 @@ dataset = dataset.apply_mask(mask=mask) +bulge = al.model_util.mge_model_from( + mask_radius=mask_radius, + total_gaussians=20, + gaussian_per_basis=1, + centre_prior_is_uniform=False, +) + model = af.Collection( galaxies=af.Collection( - lens=af.Model(al.Galaxy, redshift=0.5, mass=al.mp.Isothermal), - source=af.Model(al.Galaxy, redshift=1.0, bulge=al.lp.Sersic), + lens=af.Model( + al.Galaxy, + redshift=0.5, + mass=al.mp.Isothermal, + shear=al.mp.ExternalShear, + ), + source=af.Model(al.Galaxy, redshift=1.0, bulge=bulge, disk=None), ), ) @@ -517,19 +529,19 @@ samples = samples.with_paths( [ ("galaxies", "lens", "mass", "einstein_radius"), - ("galaxies", "source", "bulge", "sersic_index"), + ("galaxies", "lens", "shear", "gamma_1"), ] ) print( "All parameters of the very first sample (containing only the lens mass's einstein radius and " - "source bulge's sersic index)." + "the first component of the lens's external shear)." ) print(samples.parameter_lists[0]) print( "Maximum Log Likelihood Model Instances (containing only the lens mass's einstein radius and " - "source bulge's sersic index):\n" + "the first component of the lens's external shear):\n" ) print(samples.max_log_likelihood(as_instance=False)) @@ -544,12 +556,12 @@ samples = result.samples samples = samples.with_paths( - ["galaxies.lens.mass.einstein_radius", "galaxies.source.bulge.sersic_index"] + ["galaxies.lens.mass.einstein_radius", "galaxies.lens.shear.gamma_1"] ) print( "All parameters of the very first sample (containing only the lens mass's einstein radius and " - "source bulge's sersic index)." + "the first component of the lens's external shear)." ) """ diff --git a/scripts/guides/results/start_here.py b/scripts/guides/results/start_here.py index 99a341aa0..d3a415339 100644 --- a/scripts/guides/results/start_here.py +++ b/scripts/guides/results/start_here.py @@ -156,7 +156,12 @@ model = af.Collection( galaxies=af.Collection( - lens=af.Model(al.Galaxy, redshift=0.5, mass=al.mp.Isothermal), + lens=af.Model( + al.Galaxy, + redshift=0.5, + mass=al.mp.Isothermal, + shear=al.mp.ExternalShear, + ), source=af.Model(al.Galaxy, redshift=1.0, bulge=bulge, disk=None), ), )