This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
pip install -e ".[dev]"# All tests
python -m pytest test_autogalaxy/
# Single test file
python -m pytest test_autogalaxy/galaxy/test_galaxy.py
# Single test
python -m pytest test_autogalaxy/galaxy/test_galaxy.py::TestGalaxy::test_name
# With output
python -m pytest test_autogalaxy/imaging/test_fit_imaging.py -sWhen 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:
NUMBA_CACHE_DIR=/tmp/numba_cache MPLCONFIGDIR=/tmp/matplotlib python -m pytest test_autogalaxy/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.
black autogalaxy/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.
PYAUTO_OUTPUT_MODE=1 python scripts/my_script.py
# -> ./output_mode/my_script/0_fit.png, 1_tracer.png, ...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.
PyAutoGalaxy is a Bayesian galaxy morphology fitting library. It depends on two sibling packages:
autoarray– low-level data structures (grids, masks, arrays, imaging/interferometer datasets, inversions/pixelizations)autofit– non-linear search / model-fitting framework (definesaf.Analysis,af.ModelInstance,af.Result)
GeometryProfile (geometry_profiles.py)
├── SphProfile
└── EllProfile
├── LightProfile (profiles/light/abstract.py) + OperateImage mixin
│ ├── lp.Sersic, lp.Exponential, lp.Gaussian, etc. (profiles/light/standard/)
│ ├── LightProfileLinear (profiles/light/linear/) - intensity solved via inversion
│ ├── LightProfileOperated (profiles/light/operated/) - PSF already applied
│ └── Basis (profiles/basis.py) - collection of profiles for MGE / shapelets
└── MassProfile (profiles/mass/abstract/abstract.py) + OperateDeflections mixin
├── mp.Isothermal, mp.NFW, mp.PowerLaw, etc.
└── stellar, dark, total subdirectories
Galaxy (galaxy/galaxy.py) - inherits af.ModelObject, OperateImageList, OperateDeflections
└── holds arbitrary kwargs: light profiles, mass profiles, pixelizations, etc.
Galaxies (galaxy/galaxies.py) - a List[Galaxy] with group operations
Each dataset type has a Fit* class that orchestrates the full fitting pipeline:
FitImaging(imaging/fit_imaging.py) – CCD imagingFitInterferometer(interferometer/fit_interferometer.py) – ALMA/interferometryFitQuantity(quantity/fit_quantity.py) – arbitrary quantity datasetsFitEllipse(ellipse/fit_ellipse.py) – isophote/ellipse fitting
All inherit from AbstractFitInversion (abstract_fit.py), which handles the linear algebra inversion step when LightProfileLinear or pixelization-based profiles are present.
Each dataset type has an Analysis* class that implements log_likelihood_function:
AnalysisImaging(imaging/model/analysis.py)AnalysisInterferometer(interferometer/model/analysis.py)AnalysisQuantity(quantity/model/analysis.py)AnalysisEllipse(ellipse/model/analysis.py)
These inherit from AnalysisDataset → Analysis (in analysis/analysis/), which inherits af.Analysis. The log_likelihood_function builds a Fit* object from the af.ModelInstance and returns its figure_of_merit.
Profile methods that consume a grid and return an array, grid, or vector use decorators from autoarray.structures.decorators. These ensure the output type matches the input grid type:
| Decorator | Grid2D input |
Grid2DIrregular input |
|---|---|---|
@aa.grid_dec.to_array |
Array2D |
ArrayIrregular |
@aa.grid_dec.to_grid |
Grid2D |
Grid2DIrregular |
@aa.grid_dec.to_vector_yx |
VectorYX2D |
VectorYX2DIrregular |
The @aa.grid_dec.transform decorator (always stacked below the output decorator) shifts and rotates the grid to the profile's reference frame before passing it to the function body.
The canonical stacking order is:
@aa.grid_dec.to_array # outermost: wraps output
@aa.grid_dec.transform # innermost: transforms grid
def convergence_2d_from(self, grid, xp=np, **kwargs):
y = grid.array[:, 0] # use .array to get raw numpy/jax array
x = grid.array[:, 1]
return ... # return raw array; decorator wraps itKey rule: the function body must return a raw array (not an autoarray). The decorator handles wrapping. Access grid coordinates via grid.array[:, 0] / grid.array[:, 1] (not grid[:, 0]), because after @transform the grid is still an autoarray object and .array is the safe way to extract the underlying data for both numpy and jax backends.
See PyAutoArray's CLAUDE.md for full details on the decorator internals.
The codebase is designed so that NumPy is the default everywhere and JAX is opt-in. JAX is never imported at module level — it is only imported locally inside functions when explicitly requested.
The xp parameter pattern is the single point of control:
xp=np(default throughout) — pure NumPy path, no JAX dependency at runtimexp=jnp— JAX path, importsjax/jax.numpylocally inside the function
This means:
- Unit tests (
test_autogalaxy/) always run on the NumPy path. No test should import JAX or passxp=jnpunless it is explicitly testing the JAX path. - Integration tests (in
autogalaxy_workspace_test/) are where the JAX path is exercised, typically wrapped injax.jitto test both correctness and compilation. conftest.pyforces JAX backend initialisation before the test suite runs, but this only ensures JAX is available — it does not switch the default backend.
AbstractFitInversion.use_jax tracks whether a fit was constructed with JAX. AnalysisImaging has use_jax: bool = True to opt into the JAX path for model-fitting.
When adding a new function that should support JAX:
- Default the parameter to
xp=np - Guard any JAX imports with
if xp is not np:and importjax/jax.numpylocally inside that branch - Add the NumPy implementation as the default path (finite-difference,
np.*calls, etc.) - Add a JAX implementation in the guarded branch (e.g.
jax.jacfwd,jnp.vectorize) - Verify correctness by comparing both paths in
autogalaxy_workspace_test/scripts/
Autoarray types (Array2D, ArrayIrregular, VectorYX2DIrregular, etc.) are not registered as JAX pytrees. This means:
- Constructing them inside a JIT trace is fine (Python code runs normally during tracing)
- Returning them as the output of a
jax.jit-compiled function fails withTypeError: ... is not a valid JAX type
Functions decorated with @aa.grid_dec.to_array / @to_vector_yx wrap their return value in an autoarray type. This wrapping is safe for intermediate calls (the autoarray object is consumed by downstream Python code). However, if such a function is the outermost call inside a jax.jit lambda, its return value will fail at the JIT boundary.
The solution is the if xp is np: guard in the function body:
def convergence_2d_via_hessian_from(self, grid, xp=np):
convergence = 0.5 * (hessian_yy + hessian_xx)
if xp is np:
return aa.ArrayIrregular(values=convergence) # numpy: wrapped
return convergence # jax: raw jax.ArrayThis pattern is applied throughout autogalaxy/operate/lens_calc.py. Functions that are only ever called as intermediate steps (e.g. deflections_yx_2d_from) do NOT need this guard — their autoarray wrappers are never the JIT output.
LightProfileLinear subclasses do not take an intensity parameter—it is solved via a linear inversion (provided by autoarray). The GalaxiesToInversion class (galaxy/to_inversion.py) handles converting galaxies with linear profiles or pixelizations into the inversion objects needed by autoarray.
AdaptImages (analysis/adapt_images/) stores per-galaxy model images from a previous search. These are passed to subsequent searches to drive adaptive mesh/regularization schemes. galaxy_name_image_dict_via_result_from extracts adapt images from a Result.
Default priors, visualization settings, and general config live in autogalaxy/config/. Tests push a local config directory via conf.instance.push(...) in test_autogalaxy/conftest.py.
OperateImage(operate/image.py) – providesblurred_image_2d_from,visibilities_from, etc. on light objectsOperateDeflections(operate/deflections.py) – provides deflection-related operations on mass objects
Both are mixin classes inherited by LightProfile, MassProfile, Galaxy, and Galaxies.
Scripts in autogalaxy_workspace and autogalaxy_workspace_test use """...""" docstring blocks as prose commentary throughout — not # comments. Every script opens with a module-level docstring (title + underline + description), and each logical section of code is preceded by a """...""" block with a __Section Name__ header explaining what follows. See any script in autogalaxy_workspace/scripts/ for examples of this style.
The autogalaxy_workspace at /mnt/c/Users/Jammy/Code/PyAutoJAX/autogalaxy_workspace contains runnable examples and tutorials. Key locations:
start_here.ipynb/start_here.py– entry point overview of the APIscripts/imaging/– end-to-end scripts:simulator.py,fit.py,modeling.py,likelihood_function.py,features/scripts/interferometer/,scripts/ellipse/,scripts/multi/– same structure for other dataset typesscripts/howtogalaxy/– chapter-by-chapter tutorial scripts (chapters 1–4 + optional)notebooks/– Jupyter notebook equivalents of allscripts/scripts/guides/– topic guides (e.g. linear profiles, pixelizations, chaining)config/– workspace-level config that overrides package defaults when running workspace scripts
When importing autogalaxy as ag:
ag.lp.*– standard light profilesag.lp_linear.*– linear light profilesag.lp_operated.*– operated light profilesag.lp_basis.*/ag.lp_snr.*– basis and SNR profilesag.mp.*– mass profilesag.lmp.*– light+mass profilesag.ps.*– point sourcesag.Galaxy,ag.Galaxiesag.FitImaging,ag.AnalysisImaging,ag.SimulatorImaging
All files in this project must use Unix line endings (LF, \n). Windows/DOS line endings (CRLF, \r\n) will break Python files on HPC systems.
When writing or editing any file, always produce Unix line endings. Never write \r\n line endings.
After creating or copying files, verify and convert if needed:
# Check for DOS line endings
file autogalaxy/galaxy/galaxy.py # should say "ASCII text", not "CRLF"
# Convert all Python files in the project
find . -type f -name "*.py" | xargs dos2unixPrefer simple shell commands. Avoid chaining with && or pipes.