Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions .github/scripts/check_tutorials_complete.py
Original file line number Diff line number Diff line change
@@ -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 "."))
20 changes: 20 additions & 0 deletions .github/workflows/tutorials_complete.yml
Original file line number Diff line number Diff line change
@@ -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 .
3 changes: 2 additions & 1 deletion llms-full.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
42 changes: 42 additions & 0 deletions notebooks/chapter_1_introduction/tutorial_4_methods.ipynb
Original file line number Diff line number Diff line change
@@ -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": {},
Expand Down Expand Up @@ -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": {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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."
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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."
]
Expand Down
23 changes: 22 additions & 1 deletion notebooks/chapter_optional/tutorial_searches.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
28 changes: 28 additions & 0 deletions scripts/chapter_1_introduction/tutorial_4_methods.py
Original file line number Diff line number Diff line change
@@ -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__
"""
2 changes: 2 additions & 0 deletions scripts/chapter_2_modeling/tutorial_4_dealing_with_failure.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
"""
Original file line number Diff line number Diff line change
Expand Up @@ -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.
"""
11 changes: 11 additions & 0 deletions scripts/chapter_optional/tutorial_searches.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
"""
8 changes: 5 additions & 3 deletions workspace_index.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand Down
Loading