Skip to content

Commit 0492590

Browse files
Jammy2211Jammy2211claude
authored
docs: consolidate agent instructions into canonical AGENTS.md (#569)
Merge the parallel CLAUDE.md/AGENTS.md into one agent-agnostic AGENTS.md (uniform skeleton) and reduce CLAUDE.md to an @AGENTS.md import stub. Keep the two JIT-boundary patterns (the existing correct account), link the JAX/decorator deep dive in PyAutoArray, point science context at autolens_assistant/wiki/literature/, and state import direction (top of the stack), CI (3.12 + 3.13, black advisory), requires-python >=3.9. Co-authored-by: Jammy2211 <JNightingale2211@gmail.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent ae4a27a commit 0492590

2 files changed

Lines changed: 107 additions & 233 deletions

File tree

AGENTS.md

Lines changed: 102 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,124 @@
11
# PyAutoLens — Agent Instructions
22

3-
**PyAutoLens** is a Python library for strong gravitational lens modeling, built on PyAutoGalaxy. It adds multi-plane ray-tracing via the `Tracer` object and lensing-specific fit/analysis classes.
3+
Canonical, agent-agnostic instructions for this repo. `CLAUDE.md` imports this
4+
file; any tool that does not process `@`-imports should read this directly.
45

5-
## Setup
6+
## What this repo is
67

7-
```bash
8-
pip install -e ".[dev]"
9-
```
8+
**PyAutoLens** (package `autolens`) is the strong gravitational-lensing layer
9+
built on PyAutoGalaxy. It adds multi-plane ray-tracing via the `Tracer`, and
10+
lensing-specific `Fit*` / `Analysis*` classes for imaging, interferometer, and
11+
point-source datasets.
12+
13+
Dependency direction: autolens sits at the top of the stack and may import all
14+
four layers below it — **autogalaxy**, **autoarray**, **autofit**, and
15+
**autoconf**. Nothing in the ecosystem imports autolens.
1016

11-
## Running Tests
17+
## Related repos
18+
19+
- **Source siblings (all upstream):** PyAutoConf, PyAutoArray, PyAutoFit,
20+
PyAutoGalaxy.
21+
- **autolens_workspace** — runnable tutorials/examples (`../autolens_workspace`).
22+
- **autolens_workspace_test** — integration + JAX/likelihood parity scripts.
23+
- **autolens_profiling** — performance/profiling harness (`../autolens_profiling`).
24+
- **HowToLens** — the lecture-style tutorial series (`../HowToLens`).
25+
- **docs/** — Sphinx source; published to ReadTheDocs.
26+
- **Science context:** the strong-lensing knowledge wiki at
27+
`autolens_assistant/wiki/literature/` (concepts, entities, sources) — mass
28+
models, source reconstruction, degeneracies, substructure, surveys.
29+
30+
## Quick commands
1231

1332
```bash
14-
python -m pytest test_autolens/
15-
python -m pytest test_autolens/lens/test_tracer.py
16-
python -m pytest test_autolens/imaging/test_fit_imaging.py -s
33+
pip install -e ".[dev]" # install with dev/test extras
34+
python -m pytest test_autolens/ # full test suite
35+
python -m pytest test_autolens/lens/test_tracer.py # one focused test (add -s for output)
36+
black autolens/ # formatter (advisory — not gated)
1737
```
1838

19-
### Sandboxed / Codex runs
39+
In a sandboxed / restricted environment, point numba and matplotlib at
40+
writable caches:
2041

2142
```bash
2243
NUMBA_CACHE_DIR=/tmp/numba_cache MPLCONFIGDIR=/tmp/matplotlib python -m pytest test_autolens/
2344
```
2445

25-
## Key Architecture
46+
## CI / definition of green
47+
48+
PRs must pass `pytest --cov` on the CI matrix (Python 3.12 **and** 3.13). There
49+
is no black/ruff/flake8 gate — formatting is advisory. (`requires-python` in
50+
`pyproject.toml` is `>=3.9`.)
2651

27-
- **Tracer** (`lens/tracer.py`): groups galaxies by redshift plane, performs multi-plane ray-tracing
28-
- **Fit classes**: `FitImaging`, `FitInterferometer`, `FitPointDataset` — extend autogalaxy equivalents with lensing
29-
- **Analysis classes**: `AnalysisImaging`, `AnalysisInterferometer`, `AnalysisPoint`
30-
- **Namespace**: `al.mp.*` (mass), `al.lp.*` (light), `al.Galaxy`, `al.Tracer`
52+
## Configuration & defaults
3153

32-
## Dependencies
54+
autoconf supplies the packaged defaults under `autolens/config/`. Workspaces
55+
override them via their own `config/` directory; the test suite pushes a local
56+
config dir via `conf.instance.push(...)` in `test_autolens/conftest.py`. When a
57+
change adds a new config key, mirror it into the packaged defaults so
58+
downstream workspaces inherit it.
3359

34-
- `autogalaxy` — galaxy morphology, profiles, single-plane fitting
35-
- `autoarray` — data structures, grids, masks, inversions
36-
- `autofit` — non-linear search and model-fitting framework
60+
## JAX & `xp`
3761

38-
## Key Rules
62+
NumPy is the default everywhere; JAX is opt-in and never imported at module
63+
level. `xp=np` (default) selects NumPy; `xp=jnp` selects JAX (imported locally).
64+
Thread `xp` through **every** nested call — a missed site silently defaults to
65+
`xp=np` and fails when a tracer hits an `np.*` op. Two patterns cross the
66+
`jax.jit` boundary: the `if xp is np:` **guard** for raw `jax.Array` returns
67+
(the `LensCalc` hessian methods), and **pytree registration** for functions
68+
returning real wrappers/structured objects — `FitImaging`, `Tracer`, and
69+
`DatasetModel` register via `register_instance_pytree`, so
70+
`jax.jit(analysis.fit_from)(instance)` returns a real `FitImaging` with
71+
`jax.Array` leaves.
3972

40-
- The `xp` parameter controls NumPy vs JAX: `xp=np` (default) or `xp=jnp`
41-
- Functions inside `jax.jit` must guard autoarray wrapping with `if xp is np:`
42-
- Decorated functions return **raw arrays** — the decorator wraps them
43-
- All files must use Unix line endings (LF)
44-
- Format with `black autolens/`
73+
**Unit tests are NumPy-only.** A JAX/`xp` change is validated only by the
74+
parity scripts in `autolens_workspace_test` (`jax.jit` round-trip +
75+
`fitness._vmap` batch eval) — never by `test_autolens/`.
76+
77+
Full detail lives in PyAutoArray:
78+
**[`PyAutoArray/docs/agents/jax_and_decorators.md`](../PyAutoArray/docs/agents/jax_and_decorators.md)**.
79+
80+
## Public API
81+
82+
The public surface is defined authoritatively in `autolens/__init__.py` — read
83+
it rather than trusting a hand-maintained namespace table. Canonical import:
84+
85+
```python
86+
import autolens as al
87+
```
4588

46-
## Working on Issues
89+
Profiles re-export from autogalaxy (`al.mp.*`, `al.lp.*`) alongside `al.Galaxy`,
90+
`al.Tracer`, `al.FitImaging`/`al.AnalysisImaging`, and the point-source classes.
91+
92+
## Key rules / footguns
93+
94+
- Import direction: autolens may use all four upstream packages; nothing
95+
imports autolens.
96+
- Grid-decorated profile methods return a **raw array** (the decorator wraps
97+
it); write `aa.decorators.*` and read coordinates via `grid.array[:, 0]`.
98+
- All files use Unix line endings (LF, `\n`) — never `\r\n`.
99+
100+
## Working on issues
47101

48102
1. Read the issue description and any linked plan.
49-
2. Identify affected files and write your changes.
50-
3. Run the full test suite: `python -m pytest test_autolens/`
51-
4. Ensure all tests pass before opening a PR.
52-
5. If changing public API, note the change in your PR description — downstream workspaces may need updates.
53-
## Never rewrite history
54-
55-
NEVER perform these operations on any repo with a remote:
56-
57-
- `git init` in a directory already tracked by git
58-
- `rm -rf .git && git init`
59-
- Commit with subject "Initial commit", "Fresh start", "Start fresh", "Reset
60-
for AI workflow", or any equivalent message on a branch with a remote
61-
- `git push --force` to `main` (or any branch tracked as `origin/HEAD`)
62-
- `git filter-repo` / `git filter-branch` on shared branches
63-
- `git rebase -i` rewriting commits already pushed to a shared branch
64-
65-
If the working tree needs a clean state, the **only** correct sequence is:
66-
67-
git fetch origin
68-
git reset --hard origin/main
69-
git clean -fd
70-
71-
This applies equally to humans, local Claude Code, cloud Claude agents, Codex,
72-
and any other agent. The "Initial commit — fresh start for AI workflow" pattern
73-
that appeared independently on origin and local for three workspace repos is
74-
exactly what this rule prevents — it costs ~40 commits of redundant local work
75-
every time it happens.
103+
2. Identify affected files and make the change.
104+
3. Run the full suite: `python -m pytest test_autolens/`.
105+
4. If you changed public API, say so explicitly — the workspaces and
106+
downstream pipelines may need updates.
107+
5. Ensure all tests pass before opening a PR.
108+
109+
## Deep dives
110+
111+
- [`PyAutoArray/docs/agents/jax_and_decorators.md`](../PyAutoArray/docs/agents/jax_and_decorators.md)
112+
— decorator system, `xp` backend pattern, and the `jax.jit` boundary.
113+
114+
## Clean state
115+
116+
Never rewrite history on a repo with a remote (no `git init` over a tracked
117+
tree, no force-push to `main`, no rebasing pushed shared branches). To reset a
118+
dirty tree the only correct sequence is:
119+
120+
```bash
121+
git fetch origin
122+
git reset --hard origin/main
123+
git clean -fd
124+
```

CLAUDE.md

Lines changed: 5 additions & 180 deletions
Original file line numberDiff line numberDiff line change
@@ -1,180 +1,5 @@
1-
# CLAUDE.md
2-
3-
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4-
5-
## Commands
6-
7-
### Install
8-
```bash
9-
pip install -e ".[dev]"
10-
```
11-
12-
### Run Tests
13-
```bash
14-
# All tests
15-
python -m pytest test_autolens/
16-
17-
# Single test file
18-
python -m pytest test_autolens/lens/test_tracer.py
19-
20-
# With output
21-
python -m pytest test_autolens/imaging/test_fit_imaging.py -s
22-
```
23-
24-
### Codex / sandboxed runs
25-
26-
When running Python from Codex or any restricted environment, set writable cache directories so `numba` and `matplotlib` do not fail on unwritable home or source-tree paths:
27-
28-
```bash
29-
NUMBA_CACHE_DIR=/tmp/numba_cache MPLCONFIGDIR=/tmp/matplotlib python -m pytest test_autolens/
30-
```
31-
32-
This workspace is often imported from `/mnt/c/...` and Codex may not be able to write to module `__pycache__` directories or `/home/jammy/.cache`, which can cause import-time `numba` caching failures without this override.
33-
34-
### Formatting
35-
```bash
36-
black autolens/
37-
```
38-
39-
### Plot Output Mode
40-
41-
Set `PYAUTO_OUTPUT_MODE=1` to capture every figure produced by a script into numbered PNG files in `./output_mode/<script_name>/`. This is useful for visually inspecting all plots from an integration test without needing a display.
42-
43-
```bash
44-
PYAUTO_OUTPUT_MODE=1 python scripts/my_script.py
45-
# -> ./output_mode/my_script/0_fit.png, 1_tracer.png, ...
46-
```
47-
48-
When this env var is set, all `save_figure`, `subplot_save`, and `_save_subplot` calls are intercepted — the normal output path is bypassed and figures are written sequentially to the output_mode directory instead.
49-
50-
## Architecture
51-
52-
**PyAutoLens** is the gravitational lensing layer built on top of PyAutoGalaxy. It adds multi-plane ray-tracing, the `Tracer` object, and lensing-specific fit classes. It depends on:
53-
- **`autogalaxy`** — galaxy morphology, mass/light profiles, single-plane fitting
54-
- **`autoarray`** — low-level data structures (grids, masks, arrays, datasets, inversions)
55-
- **`autofit`** — non-linear search / model-fitting framework
56-
57-
### Core Class Hierarchy
58-
59-
```
60-
Tracer (lens/tracer.py)
61-
└── List[List[Galaxy]] — galaxies grouped by redshift plane
62-
├── ray-traces from source to lens to observer
63-
├── delegates to autogalaxy Galaxy/Galaxies for per-plane operations
64-
└── returns lensed images, deflection maps, convergence, magnification
65-
```
66-
67-
### Dataset Types and Fit Classes
68-
69-
| Dataset | Fit class | Analysis class |
70-
|---|---|---|
71-
| `aa.Imaging` | `FitImaging` | `AnalysisImaging` |
72-
| `aa.Interferometer` | `FitInterferometer` | `AnalysisInterferometer` |
73-
| Point source | `FitPointDataset` | `AnalysisPoint` |
74-
75-
All inherit from the corresponding `autogalaxy` base classes (`ag.FitImaging`, etc.) and extend them with multi-plane lensing via the `Tracer`.
76-
77-
### Key Directories
78-
79-
```
80-
autolens/
81-
lens/ Tracer, ray-tracing, multi-plane deflection logic
82-
imaging/ FitImaging, AnalysisImaging
83-
interferometer/ FitInterferometer, AnalysisInterferometer
84-
point/ Point-source datasets, fits, and analysis
85-
analysis/ Shared analysis base classes, adapt images
86-
aggregator/ Scraping results from autofit output directories
87-
plot/ Visualisation for all data types
88-
```
89-
90-
## Decorator System (from autoarray)
91-
92-
PyAutoLens inherits the same decorator conventions as PyAutoGalaxy. Mass and light profile methods that take a grid and return an array/grid/vector are decorated with:
93-
94-
| Decorator | `Grid2D`| `Grid2DIrregular`|
95-
|---|---|---|
96-
| `@aa.grid_dec.to_array` | `Array2D` | `ArrayIrregular` |
97-
| `@aa.grid_dec.to_grid` | `Grid2D` | `Grid2DIrregular` |
98-
| `@aa.grid_dec.to_vector_yx` | `VectorYX2D` | `VectorYX2DIrregular` |
99-
100-
The `@aa.grid_dec.transform` decorator (always innermost) transforms the grid to the profile's reference frame. Standard stacking:
101-
102-
```python
103-
@aa.grid_dec.to_array
104-
@aa.grid_dec.transform
105-
def convergence_2d_from(self, grid, xp=np, **kwargs):
106-
y = grid.array[:, 0] # .array extracts raw numpy/jax array
107-
x = grid.array[:, 1]
108-
return ... # raw array — decorator wraps it
109-
```
110-
111-
The function body must return a **raw array**. Use `grid.array[:, 0]` (not `grid[:, 0]`) to access coordinates safely for both numpy and jax backends.
112-
113-
See PyAutoArray's `CLAUDE.md` for full decorator internals.
114-
115-
## JAX Support
116-
117-
The `xp` parameter pattern controls the backend:
118-
- `xp=np` (default) — pure NumPy, no JAX dependency
119-
- `xp=jnp` — JAX path; `jax`/`jax.numpy` imported locally inside the function only
120-
121-
### JAX and the `jax.jit` boundary
122-
123-
Two patterns coexist for crossing the JIT boundary:
124-
125-
**Pattern 1: `if xp is np:` guard (raw `jax.Array` return).** Functions intended to be called directly inside `jax.jit` as the outermost op — where no wrapper is needed on the JAX path — guard their autoarray wrapping:
126-
127-
```python
128-
def convergence_2d_via_hessian_from(self, grid, xp=np):
129-
convergence = 0.5 * (hessian_yy + hessian_xx)
130-
131-
if xp is np:
132-
return aa.ArrayIrregular(values=convergence) # numpy: wrapped
133-
return convergence # jax: raw jax.Array
134-
```
135-
136-
All `LensCalc` hessian-derived methods use this pattern. Intermediate helpers (e.g. `deflections_yx_2d_from`) don't need the guard — they're consumed by downstream Python before the JIT boundary.
137-
138-
**Pattern 2: pytree-registered wrapper return.** Functions that must return a real autoarray wrapper (or a structured object built from them) opt in to JAX pytree registration. `AbstractNDArray` auto-registers its subclass with `jax.tree_util` the first time an instance is built with `xp=jnp` (via `autoarray.abstract_ndarray._register_as_pytree`). Higher-level types (`FitImaging`, `Tracer`, `DatasetModel`) use `autoarray.abstract_ndarray.register_instance_pytree(cls, no_flatten=...)`, which flattens `__dict__` and carries `no_flatten` names through `aux_data` for per-analysis constants (dataset, settings, cosmology). `AnalysisImaging._register_fit_imaging_pytrees` wires these up when `use_jax=True`, so `jax.jit(analysis.fit_from)(instance)` returns a real `FitImaging` with `jax.Array` leaves.
139-
140-
### `LensCalc` (autogalaxy)
141-
142-
The hessian-derived lensing quantities (`convergence_2d_via_hessian_from`, `shear_yx_2d_via_hessian_from`, `magnification_2d_via_hessian_from`, `magnification_2d_from`, `tangential_eigen_value_from`, `radial_eigen_value_from`) all implement the `if xp is np:` guard in `autogalaxy/operate/lens_calc.py` and return raw `jax.Array` on the JAX path, making them safe to call inside `jax.jit`.
143-
144-
## Namespace Conventions
145-
146-
When importing `autolens as al`:
147-
- `al.mp.*` — mass profiles (re-exported from autogalaxy)
148-
- `al.lp.*` — light profiles (re-exported from autogalaxy)
149-
- `al.Galaxy`, `al.Galaxies`
150-
- `al.Tracer`
151-
- `al.FitImaging`, `al.AnalysisImaging`, `al.SimulatorImaging`
152-
- `al.FitInterferometer`, `al.AnalysisInterferometer`
153-
- `al.FitPointDataset`, `al.AnalysisPoint`
154-
155-
## Line Endings — Always Unix (LF)
156-
157-
All files **must use Unix line endings (LF, `\n`)**. Never write `\r\n` line endings.
158-
## Never rewrite history
159-
160-
NEVER perform these operations on any repo with a remote:
161-
162-
- `git init` in a directory already tracked by git
163-
- `rm -rf .git && git init`
164-
- Commit with subject "Initial commit", "Fresh start", "Start fresh", "Reset
165-
for AI workflow", or any equivalent message on a branch with a remote
166-
- `git push --force` to `main` (or any branch tracked as `origin/HEAD`)
167-
- `git filter-repo` / `git filter-branch` on shared branches
168-
- `git rebase -i` rewriting commits already pushed to a shared branch
169-
170-
If the working tree needs a clean state, the **only** correct sequence is:
171-
172-
git fetch origin
173-
git reset --hard origin/main
174-
git clean -fd
175-
176-
This applies equally to humans, local Claude Code, cloud Claude agents, Codex,
177-
and any other agent. The "Initial commit — fresh start for AI workflow" pattern
178-
that appeared independently on origin and local for three workspace repos is
179-
exactly what this rule prevents — it costs ~40 commits of redundant local work
180-
every time it happens.
1+
# PyAutoLens — agent instructions
2+
The canonical, agent-agnostic instructions live in `AGENTS.md`. Claude Code loads them
3+
via the import below; if your tool does not process `@`-imports, open `AGENTS.md` in
4+
this directory and read it directly.
5+
@AGENTS.md

0 commit comments

Comments
 (0)