diff --git a/.github/scripts/check_tutorials_complete.py b/.github/scripts/check_tutorials_complete.py new file mode 100644 index 0000000..670b8dd --- /dev/null +++ b/.github/scripts/check_tutorials_complete.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +"""Fail if any HowTo tutorial script looks truncated. + +Several tutorial scripts were once cut off mid-generation (a long file was +re-emitted and the output was severed), losing all content below the cut and +leaving a script that ends part-way through — typically on a docstring that +promises a plot or code block which never appears. This check guards against +that regression recurring. + +A tutorial is considered *complete* when it contains a recognised terminal +section marker (``__Wrap Up__`` or ``__Summary__``). A truncated script never +reaches its wrap-up, so the absence of the marker is a reliable signal that the +script lost content. Deliberate "not written yet" stub tutorials still carry a +``__Wrap Up__`` section, so they pass. + +A second, cheaper guard flags any script whose final docstring block ends on a +colon (``:``) — the classic "a plot/code block follows" promise left dangling +by a mid-docstring cutoff. + +Run from the repo root:: + + python scripts/check_tutorials_complete.py + +Exit status is non-zero if any tutorial fails, listing each offender. +""" +from __future__ import annotations + +import re +import sys +from pathlib import Path + +TERMINAL_MARKERS = ("__wrap up__", "__summary__") + + +def final_docstring(text: str) -> str | None: + """Return the last triple-quoted block if it sits at the end of the file.""" + blocks = list(re.finditer(r'"""(.*?)"""', text, re.DOTALL)) + if not blocks: + return None + last = blocks[-1] + if text[last.end():].strip() == "": + return last.group(1) + return None + + +def check(path: Path) -> str | None: + """Return a failure reason for a truncated-looking tutorial, else None.""" + text = path.read_text(encoding="utf-8") + if not any(marker in text.lower() for marker in TERMINAL_MARKERS): + return "no __Wrap Up__/__Summary__ terminal section (looks truncated)" + trailing = final_docstring(text) + if trailing is not None and trailing.strip().endswith(":"): + return "final docstring ends on ':' (dangling promise of a following block)" + return None + + +def main(root: str) -> int: + scripts_dir = Path(root) / "scripts" + files = sorted(scripts_dir.rglob("tutorial_*.py")) + + failures = [(f, reason) for f in files if (reason := check(f)) is not None] + + print(f"Checked {len(files)} tutorial scripts under {scripts_dir}.") + if failures: + print(f"\n{len(failures)} tutorial(s) look incomplete / truncated:\n") + for f, reason in failures: + print(f" [FAIL] {f.relative_to(root)} — {reason}") + print( + "\nEach tutorial must end with a `__Wrap Up__` (or `__Summary__`) " + "section. If a script is genuinely truncated, restore its lost " + "content; if it is complete, add the terminal section." + ) + return 1 + + print("All tutorial scripts have a terminal section — none look truncated.") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv[1] if len(sys.argv) > 1 else ".")) diff --git a/.github/workflows/tutorials_complete.yml b/.github/workflows/tutorials_complete.yml new file mode 100644 index 0000000..a3fa780 --- /dev/null +++ b/.github/workflows/tutorials_complete.yml @@ -0,0 +1,20 @@ +name: Tutorials Complete + +# Guards against tutorial scripts being truncated / cut off mid-generation (a +# long file re-emitted with the output severed, losing every section below the +# cut). Each tutorial_*.py must end with a `__Wrap Up__` (or `__Summary__`) +# terminal section; the check is pure-stdlib and needs no library install. +# See scripts/check_tutorials_complete.py. + +on: [push, pull_request] + +jobs: + tutorials-complete: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.13" + - name: Check tutorials are not truncated + run: python .github/scripts/check_tutorials_complete.py . diff --git a/llms-full.txt b/llms-full.txt index 4a08792..5784f2f 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -17,7 +17,8 @@ AUTO-GENERATED by PyAutoBuild — do not edit by hand; regenerate with generate. - Contents: Initial Setup, Optics Blurring, Poisson Noise, Background Sky, Simulator, Output, Wrap Up - [Tutorial 3: Fitting](scripts/chapter_1_introduction/tutorial_3_fitting.py): In previous tutorials, we used light profiles to create simulated images of galaxies and visualized how these images would appear when captured by a CCD detector on a telescope like the Hubble Space Telescope. - Contents: Dataset, Mask, Masked Grid, Fitting, Incorrect Fit, Model Fitting, Wrap Up -- [tutorial_4_methods](scripts/chapter_1_introduction/tutorial_4_methods.py): (no summary in script docstring) +- [Tutorial 4: Methods](scripts/chapter_1_introduction/tutorial_4_methods.py): This tutorial is not written yet, but will explain in more detail the different methods used to fit and analyse galaxies. + - Contents: Wrap Up - [Tutorial 9: Summary](scripts/chapter_1_introduction/tutorial_5_summary.py): In this chapter, we have learnt that: - Contents: Initial Setup, Object Composition, Visualization, Code Design, Source Code, Wrap Up diff --git a/notebooks/chapter_1_introduction/tutorial_4_methods.ipynb b/notebooks/chapter_1_introduction/tutorial_4_methods.ipynb index 88db216..8a97a32 100644 --- a/notebooks/chapter_1_introduction/tutorial_4_methods.ipynb +++ b/notebooks/chapter_1_introduction/tutorial_4_methods.ipynb @@ -1,5 +1,25 @@ { "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Tutorial 4: Methods\n", + "===================\n", + "\n", + "This tutorial is not written yet, but will explain in more detail the different methods used to fit and analyse\n", + "galaxies.\n", + "\n", + "This tutorial is not necessary for using PyAutoGalaxy or performing galaxy analysis, so don't worry that it is not\n", + "written yet!\n", + "\n", + "Tutorial 5 summary is written and you should check that out instead!\n", + "\n", + "__Contents__\n", + "\n", + "- **Wrap Up:** Summary of the script and next steps." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -37,6 +57,28 @@ "setup_colab.setup(\"howtogalaxy\")" ] }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "\n", + "from autoconf import jax_wrapper # Sets JAX environment before other imports\n", + "\n", + "from autoconf import setup_notebook; setup_notebook()\n", + "\n", + "import autogalaxy as ag\n", + "import autogalaxy.plot as aplt" + ], + "outputs": [], + "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__Wrap Up__" + ] + }, { "cell_type": "code", "metadata": {}, diff --git a/notebooks/chapter_2_modeling/tutorial_4_dealing_with_failure.ipynb b/notebooks/chapter_2_modeling/tutorial_4_dealing_with_failure.ipynb index a3bbd1e..e0d69ca 100644 --- a/notebooks/chapter_2_modeling/tutorial_4_dealing_with_failure.ipynb +++ b/notebooks/chapter_2_modeling/tutorial_4_dealing_with_failure.ipynb @@ -721,6 +721,8 @@ " by the earlier searches. We can therefore guide each search on how to sample a complex model's parameter space \n", " in a way that can be fully generalized to any galaxy.\n", " \n", + "__Wrap Up__\n", + "\n", "To wrap up chapter 2, we have a few more tutorials, where we will discuss masking in more detail, the `Result` object\n", "and how to make **PyAutoGalaxy** run faster." ] diff --git a/notebooks/chapter_4_pixelizations/tutorial_4_bayesian_regularization.ipynb b/notebooks/chapter_4_pixelizations/tutorial_4_bayesian_regularization.ipynb index 881ae32..a5feddb 100644 --- a/notebooks/chapter_4_pixelizations/tutorial_4_bayesian_regularization.ipynb +++ b/notebooks/chapter_4_pixelizations/tutorial_4_bayesian_regularization.ipynb @@ -433,8 +433,10 @@ " values of the image pixels whose variances are increased were initially very high (e.g. they were fit poorly by the \n", " model).\n", "\n", + "__Wrap Up__\n", + "\n", "In summary, the log evidence is maximized for solutions which most accurately reconstruct the highest S/N realization of\n", - "the observed image, without over-fitting its noise and using the fewest correlated pixelization pixels. By employing \n", + "the observed image, without over-fitting its noise and using the fewest correlated pixelization pixels. By employing\n", "this framework throughout, **PyAutoGalaxy** objectively determines the final model following the principles of Bayesian\n", "analysis and Occam\u2019s Razor." ] diff --git a/notebooks/chapter_optional/tutorial_searches.ipynb b/notebooks/chapter_optional/tutorial_searches.ipynb index 536b189..bccb074 100644 --- a/notebooks/chapter_optional/tutorial_searches.ipynb +++ b/notebooks/chapter_optional/tutorial_searches.ipynb @@ -452,10 +452,31 @@ "\n", "print(\"The search has finished run - you may now continue the notebook.\")\n", "\n", - "aplt.subplot_fit_imaging(fit=result_emcee.max_log_likelihood_fit)\n" + "aplt.subplot_fit_imaging(fit=result_emcee.max_log_likelihood_fit)" ], "outputs": [], "execution_count": null + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__Wrap Up__\n", + "\n", + "This tutorial showed how the same model can be fitted using different non-linear searches (e.g. `Nautilus`, `Emcee`,\n", + "`PySwarms`). Each search explores parameter space in a different way, and the best choice depends on the\n", + "dimensionality and complexity of the model you are fitting.\n", + "\n", + "For the vast majority of models in **PyAutoGalaxy**, the default nested sampling search `Nautilus` is recommended, as\n", + "it is robust, efficient and requires little manual tuning." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [], + "outputs": [], + "execution_count": null } ], "metadata": { diff --git a/scripts/chapter_1_introduction/tutorial_4_methods.py b/scripts/chapter_1_introduction/tutorial_4_methods.py index e69de29..4d85662 100755 --- a/scripts/chapter_1_introduction/tutorial_4_methods.py +++ b/scripts/chapter_1_introduction/tutorial_4_methods.py @@ -0,0 +1,28 @@ +""" +Tutorial 4: Methods +=================== + +This tutorial is not written yet, but will explain in more detail the different methods used to fit and analyse +galaxies. + +This tutorial is not necessary for using PyAutoGalaxy or performing galaxy analysis, so don't worry that it is not +written yet! + +Tutorial 5 summary is written and you should check that out instead! + +__Contents__ + +- **Wrap Up:** Summary of the script and next steps. + +""" + +from autoconf import jax_wrapper # Sets JAX environment before other imports + +# from autoconf import setup_notebook; setup_notebook() + +import autogalaxy as ag +import autogalaxy.plot as aplt + +""" +__Wrap Up__ +""" diff --git a/scripts/chapter_2_modeling/tutorial_4_dealing_with_failure.py b/scripts/chapter_2_modeling/tutorial_4_dealing_with_failure.py index 81212d1..d326d70 100644 --- a/scripts/chapter_2_modeling/tutorial_4_dealing_with_failure.py +++ b/scripts/chapter_2_modeling/tutorial_4_dealing_with_failure.py @@ -437,6 +437,8 @@ by the earlier searches. We can therefore guide each search on how to sample a complex model's parameter space in a way that can be fully generalized to any galaxy. +__Wrap Up__ + To wrap up chapter 2, we have a few more tutorials, where we will discuss masking in more detail, the `Result` object and how to make **PyAutoGalaxy** run faster. """ diff --git a/scripts/chapter_4_pixelizations/tutorial_4_bayesian_regularization.py b/scripts/chapter_4_pixelizations/tutorial_4_bayesian_regularization.py index ee3d55b..af913a3 100644 --- a/scripts/chapter_4_pixelizations/tutorial_4_bayesian_regularization.py +++ b/scripts/chapter_4_pixelizations/tutorial_4_bayesian_regularization.py @@ -282,8 +282,10 @@ def perform_fit_with_galaxy(dataset, galaxy): values of the image pixels whose variances are increased were initially very high (e.g. they were fit poorly by the model). +__Wrap Up__ + In summary, the log evidence is maximized for solutions which most accurately reconstruct the highest S/N realization of -the observed image, without over-fitting its noise and using the fewest correlated pixelization pixels. By employing +the observed image, without over-fitting its noise and using the fewest correlated pixelization pixels. By employing this framework throughout, **PyAutoGalaxy** objectively determines the final model following the principles of Bayesian analysis and Occam’s Razor. """ diff --git a/scripts/chapter_optional/tutorial_searches.py b/scripts/chapter_optional/tutorial_searches.py index 96682dc..121a4ca 100644 --- a/scripts/chapter_optional/tutorial_searches.py +++ b/scripts/chapter_optional/tutorial_searches.py @@ -296,3 +296,14 @@ print("The search has finished run - you may now continue the notebook.") aplt.subplot_fit_imaging(fit=result_emcee.max_log_likelihood_fit) + +""" +__Wrap Up__ + +This tutorial showed how the same model can be fitted using different non-linear searches (e.g. `Nautilus`, `Emcee`, +`PySwarms`). Each search explores parameter space in a different way, and the best choice depends on the +dimensionality and complexity of the model you are fitting. + +For the vast majority of models in **PyAutoGalaxy**, the default nested sampling search `Nautilus` is recommended, as +it is robust, efficient and requires little manual tuning. +""" diff --git a/workspace_index.json b/workspace_index.json index ecc24cb..263f26c 100644 --- a/workspace_index.json +++ b/workspace_index.json @@ -65,12 +65,14 @@ "title": "Tutorial 3: Fitting" }, { - "contents": [], + "contents": [ + "Wrap Up" + ], "cross_refs": [], "notebook": "notebooks/chapter_1_introduction/tutorial_4_methods.ipynb", "path": "scripts/chapter_1_introduction/tutorial_4_methods.py", - "summary": "(no summary in script docstring)", - "title": "tutorial_4_methods" + "summary": "This tutorial is not written yet, but will explain in more detail the different methods used to fit and analyse galaxies.", + "title": "Tutorial 4: Methods" }, { "contents": [