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
59 changes: 59 additions & 0 deletions .github/workflows/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# `.github/workflows/`

CI for `autolens_profiling`. Two workflows, deliberately split.

## `lint.yml` — PR + push-to-main gate

Runs on every PR and every push to `main`. CPU-only, target wall time under 5 minutes.

What it checks:

| Step | Purpose |
|------|---------|
| `ruff check .` | Pyflakes + pycodestyle + isort + pyupgrade + flake8-bugbear (see `ruff.toml`) |
| `ruff format --check .` | Formatting parity with sister PyAutoLabs repos (black-compatible defaults) |
| `python scripts/build_readme.py --check` | Dashboard idempotence — the auto-generated tables in every section README must match what `build_readme.py` would generate from the current `results/` artifacts. Catches the "forgot to rerun the dashboard generator after dropping a new result" class of bug |
| `lychee` | Markdown link-rot across every `README.md` |
| Smoke — one script per section | Runs `likelihood/imaging/mge.py`, `simulators/imaging.py`, and `searches/nautilus/simple.py` with `AUTOLENS_PROFILING_SMOKE=1`. Every profile script reads that env var at module top and exits 0 after the import + setup section. Catches import-graph breakage (broken `sys.path` injection, missing dependency, renamed module) without running the full profile |

The smoke step does **not** produce real result artifacts — every script short-circuits before the JIT compile / sampling / FITS writes. If you need full smoke output, run `profile.yml` manually instead.

## `profile.yml` — manual + on-release profile re-run

Triggered by:

- `workflow_dispatch` (manual via the GitHub UI). Optional `sections` input lets you scope a run to one of `likelihood`, `simulators`, `searches`, or any comma-separated combination. Leave blank to run everything.
- `release: published` — when a new GitHub release is published.

What it does:

1. Runs every script under `likelihood/`, `simulators/`, and `searches/nautilus/`, producing JSON+PNG artifacts under `results/`. `continue-on-error: true` per section so a single regression doesn't block the dashboard refresh for the remaining 16+ scripts; failures emit a `::warning::` annotation and the matching dashboard cell will show `ERR`.
2. Skips `simulators/point_source.py` in the simulator loop because its default `dataset_name="simple"` overwrites the Phase 1 likelihood input JSONs (see `simulators/README.md`). Run that one manually with a non-conflicting `dataset_name` when needed.
3. Runs `python scripts/build_readme.py` to refresh every auto-generated table from the latest artifacts.
4. Commits the diff back to `main` as `github-actions[bot]` with `[skip ci]` in the subject (prevents the lint workflow from re-triggering on the auto-generated commit).

Hardware: GitHub-hosted `ubuntu-latest` (CPU). Expect Nautilus's `n_live=200` runs to take 30–60 minutes each on CPU; total job time can approach the 4-hour `timeout-minutes` budget on a full run. Self-hosted GPU runners can be added later as a separate job that appends `*_gpu*.json` artifacts to `results/` without restructuring this workflow — the dashboard's hardware-tier column extension (top-level README "Future enhancements") is the matching reader-side change.

## How to trigger `profile.yml` manually

From the GitHub UI:

1. Repo → **Actions** → **profile** workflow.
2. Click **Run workflow**.
3. (Optional) Enter a sections filter, e.g. `likelihood,simulators`, or leave blank for all.
4. Click **Run workflow**.

Or via `gh`:

```bash
gh workflow run profile --repo PyAutoLabs/autolens_profiling -F sections=likelihood
gh workflow run profile --repo PyAutoLabs/autolens_profiling # all sections
```

## Design decisions (captured for future maintainers)

- **CPU-only runners** to start. GitHub-hosted is free and the dashboard's CPU column is the most-requested baseline. Future GPU laptop / A100 columns require either self-hosted runners or external upload of `*_<hardware>_<version>.json` files; both are additive on this workflow's shape.
- **No matrix across Python versions / OSes**. Lens-modelling profiling is Linux-only in practice, and a single Python version (3.12) keeps the matrix lean.
- **No coverage reporting**. This repo has no unit tests by design — it's a scripts collection, and the smoke step + dashboard idempotence check are the practical equivalents.
- **Why `[skip ci]` rather than path filters**: the lint workflow's smoke step does run scripts, and a commit that touches only `results/` and `README.md` files could in principle re-trigger the lint workflow (which is cheap, but pointless). Subject-line `[skip ci]` is the simplest fix and is honoured by GitHub Actions natively.
- **`continue-on-error: true`** on each profile section, rather than fail-fast: a regression in one script shouldn't block the dashboard refresh for the other 16. The `::warning::` annotation surfaces the failure in the run UI and the dashboard cell will show `ERR` until the next successful refresh.
114 changes: 114 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# lint.yml — PR / push-to-main gate.
#
# Cheap, fast (<5 min), CPU-only. Runs on every PR and on push to main.
# Does NOT produce real result artifacts — the smoke step short-circuits
# every profile script via AUTOLENS_PROFILING_SMOKE=1 (lands as part of
# Phase 5; see scripts under likelihood/, simulators/, searches/nautilus/).
#
# Profile re-runs that actually produce results live in `profile.yml`.

name: lint

on:
pull_request:
push:
branches: [main]

permissions:
contents: read

jobs:
lint:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout autolens_profiling
uses: actions/checkout@v4
with:
path: autolens_profiling

- name: Checkout PyAutoConf
uses: actions/checkout@v4
with:
repository: PyAutoLabs/PyAutoConf
path: PyAutoConf
- name: Checkout PyAutoFit
uses: actions/checkout@v4
with:
repository: PyAutoLabs/PyAutoFit
path: PyAutoFit
- name: Checkout PyAutoArray
uses: actions/checkout@v4
with:
repository: PyAutoLabs/PyAutoArray
path: PyAutoArray
- name: Checkout PyAutoGalaxy
uses: actions/checkout@v4
with:
repository: PyAutoLabs/PyAutoGalaxy
path: PyAutoGalaxy
- name: Checkout PyAutoLens
uses: actions/checkout@v4
with:
repository: PyAutoLabs/PyAutoLens
path: PyAutoLens

- name: Set up Python 3.12
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install runtime dependencies
run: |
python -m pip install --upgrade pip
pip install numpy scipy matplotlib jax jaxlib
pip install nautilus-sampler==1.0.5
pip install numba
# autoconf / autofit / autoarray / autogalaxy / autolens are imported
# from the sibling checkouts via PYTHONPATH below — no install needed.

- name: Install lint tools
run: |
pip install ruff
# lychee is a Rust binary; fetch the prebuilt release.
curl -sSfL https://github.com/lycheeverse/lychee/releases/latest/download/lychee-x86_64-unknown-linux-gnu.tar.gz \
| tar -xz -C /usr/local/bin lychee

- name: ruff check
working-directory: autolens_profiling
run: ruff check .

- name: ruff format --check
working-directory: autolens_profiling
run: ruff format --check .

- name: build_readme.py --check (dashboard idempotence)
working-directory: autolens_profiling
run: python scripts/build_readme.py --check

- name: lychee — markdown link-rot
working-directory: autolens_profiling
run: |
# Conservative scope: only README.md files in this repo, exclude
# GitHub UI links that lychee tends to false-positive on.
lychee \
--exclude '^https://github\.com/.*/(issues|pull|commit|releases)/' \
--no-progress \
--accept '200..=299,429' \
--max-redirects 5 \
$(find . -name 'README.md' -not -path './results/*' -not -path './dataset/*')

- name: Smoke — one script per section
working-directory: autolens_profiling
env:
AUTOLENS_PROFILING_SMOKE: "1"
NUMBA_CACHE_DIR: /tmp/numba_cache
MPLCONFIGDIR: /tmp/matplotlib
PYTHONPATH: ${{ github.workspace }}/PyAutoConf:${{ github.workspace }}/PyAutoFit:${{ github.workspace }}/PyAutoArray:${{ github.workspace }}/PyAutoGalaxy:${{ github.workspace }}/PyAutoLens
run: |
# Each script reads AUTOLENS_PROFILING_SMOKE at module top and
# exits 0 immediately after the import + setup section. Catches
# import-graph breakage without running the full profile.
python likelihood/imaging/mge.py
python simulators/imaging.py
python searches/nautilus/simple.py
172 changes: 172 additions & 0 deletions .github/workflows/profile.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
# profile.yml — manual + on-release profile re-run + dashboard refresh.
#
# Runs the real profile scripts (NumPy + JAX paths on CPU runners),
# regenerates the JSON+PNG artifacts under results/, runs
# `scripts/build_readme.py` to refresh the dashboard tables in every
# README, and commits the diff back to main as `github-actions[bot]`
# with `[skip ci]` in the subject to avoid loops.
#
# Design decisions (deliberate, captured here for future maintainers):
#
# - **CPU-only on GitHub-hosted ubuntu-latest**. Self-hosted GPU runners
# can be added later as a separate job that appends *_gpu*.json / *_a100*.json
# artifacts without restructuring this workflow. The dashboard's
# hardware-tier column extension (see top-level README "Future
# enhancements") is the matching reader-side change.
#
# - **Manual + on release tag**, not a weekly cron. Profile scripts on
# CPU take O(minutes) each; running them on every PR or every commit
# burns CI minutes for noise. Releases are the natural cadence for
# cross-version comparison anyway.
#
# - **`github-actions[bot]` with `[skip ci]`**: needs `contents: write`.
# The `[skip ci]` tag prevents the lint workflow from re-running on the
# auto-generated commit, which would loop.
#
# - **`continue-on-error` per script**: a single regression shouldn't
# block the dashboard refresh for the other 16 scripts. The build_readme
# step still runs over whatever artifacts landed, so partial data is
# better than no data.

name: profile

on:
workflow_dispatch:
inputs:
sections:
description: "Comma-separated section list to run (likelihood,simulators,searches) — leave blank for all"
required: false
default: ""
release:
types: [published]

permissions:
contents: write

jobs:
profile:
runs-on: ubuntu-latest
timeout-minutes: 240 # 4 hours — generous upper bound for CPU runs
steps:
- name: Checkout autolens_profiling
uses: actions/checkout@v4
with:
path: autolens_profiling
token: ${{ secrets.GITHUB_TOKEN }}

- name: Checkout PyAutoConf
uses: actions/checkout@v4
with:
repository: PyAutoLabs/PyAutoConf
path: PyAutoConf
- name: Checkout PyAutoFit
uses: actions/checkout@v4
with:
repository: PyAutoLabs/PyAutoFit
path: PyAutoFit
- name: Checkout PyAutoArray
uses: actions/checkout@v4
with:
repository: PyAutoLabs/PyAutoArray
path: PyAutoArray
- name: Checkout PyAutoGalaxy
uses: actions/checkout@v4
with:
repository: PyAutoLabs/PyAutoGalaxy
path: PyAutoGalaxy
- name: Checkout PyAutoLens
uses: actions/checkout@v4
with:
repository: PyAutoLabs/PyAutoLens
path: PyAutoLens

- name: Set up Python 3.12
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install numpy scipy matplotlib jax jaxlib
pip install nautilus-sampler==1.0.5
pip install numba

- name: Run likelihood/ scripts
if: ${{ inputs.sections == '' || contains(inputs.sections, 'likelihood') }}
working-directory: autolens_profiling
continue-on-error: true
env:
NUMBA_CACHE_DIR: /tmp/numba_cache
MPLCONFIGDIR: /tmp/matplotlib
PYTHONPATH: ${{ github.workspace }}/PyAutoConf:${{ github.workspace }}/PyAutoFit:${{ github.workspace }}/PyAutoArray:${{ github.workspace }}/PyAutoGalaxy:${{ github.workspace }}/PyAutoLens
run: |
set +e # don't abort the whole step on one script's failure
for script in likelihood/imaging/*.py likelihood/interferometer/*.py \
likelihood/point_source/*.py likelihood/datacube/delaunay.py; do
echo "::group::$script"
python "$script" || echo "::warning::$script failed — see log; dashboard cell will show ERR"
echo "::endgroup::"
done

- name: Run simulators/ scripts
if: ${{ inputs.sections == '' || contains(inputs.sections, 'simulators') }}
working-directory: autolens_profiling
continue-on-error: true
env:
NUMBA_CACHE_DIR: /tmp/numba_cache
MPLCONFIGDIR: /tmp/matplotlib
PYTHONPATH: ${{ github.workspace }}/PyAutoConf:${{ github.workspace }}/PyAutoFit:${{ github.workspace }}/PyAutoArray:${{ github.workspace }}/PyAutoGalaxy:${{ github.workspace }}/PyAutoLens
run: |
set +e
for script in simulators/imaging.py simulators/interferometer.py \
simulators/cluster.py simulators/group.py simulators/multi.py; do
# point_source.py default dataset_name="simple" overwrites tracked
# likelihood inputs; skip in CI (see simulators/README.md). Manual
# runs with a non-default dataset_name remain supported.
echo "::group::$script"
python "$script" || echo "::warning::$script failed — see log; dashboard cell will show ERR"
echo "::endgroup::"
done

- name: Run searches/nautilus/ scripts
if: ${{ inputs.sections == '' || contains(inputs.sections, 'searches') }}
working-directory: autolens_profiling
continue-on-error: true
env:
NUMBA_CACHE_DIR: /tmp/numba_cache
MPLCONFIGDIR: /tmp/matplotlib
PYTHONPATH: ${{ github.workspace }}/PyAutoConf:${{ github.workspace }}/PyAutoFit:${{ github.workspace }}/PyAutoArray:${{ github.workspace }}/PyAutoGalaxy:${{ github.workspace }}/PyAutoLens
run: |
set +e
# Nautilus at n_live=200 on CPU takes O(minutes) to O(hours) per
# script. The full pair runs sequentially; expect 30-60 min total
# on the GitHub-hosted runner.
python searches/nautilus/simple.py || echo "::warning::nautilus/simple failed"
python searches/nautilus/jax.py || echo "::warning::nautilus/jax failed"

- name: Refresh dashboard tables
working-directory: autolens_profiling
run: python scripts/build_readme.py

- name: Commit and push refreshed artifacts + READMEs
working-directory: autolens_profiling
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
if [ -z "$(git status --porcelain)" ]; then
echo "No changes — exiting clean."
exit 0
fi
# Force-add tracked-but-gitignored result artifacts. Phase 4
# currently keeps results/{likelihood,simulators,searches}/
# gitignored; future hardware-tier work may flip a subset to
# tracked, in which case this `add -f` becomes redundant but
# harmless.
git add -f results/ likelihood/README.md likelihood/*/README.md \
simulators/README.md searches/README.md \
searches/nautilus/README.md README.md
git commit -m "chore: refresh profile artifacts + dashboard tables [skip ci]

Triggered by ${{ github.event_name }} (${{ github.event.release.tag_name || github.run_id }})."
git push origin HEAD:main
10 changes: 10 additions & 0 deletions likelihood/datacube/delaunay.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,16 @@
# Instrument configuration
# ---------------------------------------------------------------------------


# AUTOLENS_PROFILING_SMOKE=1 short-circuit (Phase 5 / CI lint smoke).
# Verifies the import graph + module-level setup succeeded without running
# the full profiling pipeline. Skipped entirely when the env var is unset.
import os as _smoke_os
import sys as _smoke_sys
if _smoke_os.environ.get("AUTOLENS_PROFILING_SMOKE") == "1":
print(f"[smoke] {__file__}: imports + module setup OK; exiting.")
_smoke_sys.exit(0)

INSTRUMENTS = {
"sma": {"pixel_scale": 0.1, "real_space_shape": (256, 256), "mask_radius": 3.0},
"alma": {"pixel_scale": 0.05, "real_space_shape": (256, 256), "mask_radius": 3.0},
Expand Down
10 changes: 10 additions & 0 deletions likelihood/imaging/delaunay.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,16 @@
# Instrument configuration
# ---------------------------------------------------------------------------


# AUTOLENS_PROFILING_SMOKE=1 short-circuit (Phase 5 / CI lint smoke).
# Verifies the import graph + module-level setup succeeded without running
# the full profiling pipeline. Skipped entirely when the env var is unset.
import os as _smoke_os
import sys as _smoke_sys
if _smoke_os.environ.get("AUTOLENS_PROFILING_SMOKE") == "1":
print(f"[smoke] {__file__}: imports + module setup OK; exiting.")
_smoke_sys.exit(0)

INSTRUMENTS = {
"euclid": {"pixel_scale": 0.1},
"hst": {"pixel_scale": 0.05},
Expand Down
10 changes: 10 additions & 0 deletions likelihood/imaging/mge.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,16 @@
# Instrument configuration
# ---------------------------------------------------------------------------


# AUTOLENS_PROFILING_SMOKE=1 short-circuit (Phase 5 / CI lint smoke).
# Verifies the import graph + module-level setup succeeded without running
# the full profiling pipeline. Skipped entirely when the env var is unset.
import os as _smoke_os
import sys as _smoke_sys
if _smoke_os.environ.get("AUTOLENS_PROFILING_SMOKE") == "1":
print(f"[smoke] {__file__}: imports + module setup OK; exiting.")
_smoke_sys.exit(0)

INSTRUMENTS = {
"euclid": {"pixel_scale": 0.1},
"hst": {"pixel_scale": 0.05},
Expand Down
Loading