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
57 changes: 39 additions & 18 deletions scripts/group/features/scaling_relation/modeling.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,34 +122,41 @@
"""
__Centres__

Three centre JSON files — one per tier. The same `al.from_json` pattern is used by `group/modeling.py` for
`main_lens_centres.json` and `extra_galaxies_centres.json`; we just add a third file for the scaling tier.
Centres for the main and extra tiers come from JSON files (one (y, x) tuple per galaxy each). The scaling tier loads
its centres AND luminosities from a single CSV via `al.galaxy_table_from_csv` — see the next section.
"""
main_lens_centres = al.from_json(file_path=dataset_path / "main_lens_centres.json")
extra_galaxies_centres = al.from_json(
file_path=dataset_path / "extra_galaxies_centres.json"
)
scaling_galaxies_centres = al.from_json(
file_path=dataset_path / "scaling_galaxies_centres.json"
)

print(f"Main lens centres: {main_lens_centres}")
print(f"Extra galaxies centres: {extra_galaxies_centres}")
print(f"Scaling galaxies centres: {scaling_galaxies_centres}")

"""
__Scaling Galaxy Luminosities__
__Scaling Galaxy Centres + Luminosities__

The scaling relation needs both centres AND a measured luminosity per scaling galaxy. There are two equally-supported
ways to provide them in PyAutoLens — both shown below so you can pick whichever fits your workflow.

**Option A — CSV via `al.galaxy_table_from_csv` (recommended for non-trivial galaxy counts).** The simulator writes a
`scaling_galaxies.csv` with columns `y, x, luminosity` (and optional `redshift`) alongside the centre JSONs. We load it
in one call which returns a typed `GalaxyTable` with `.centres` (a `Grid2DIrregular`), `.luminosities`, and (optionally)
`.redshifts`. This scales naturally to populations of tens or hundreds of galaxies — the source of truth lives in a
single editable file.

The scaling relation needs a measured luminosity per scaling galaxy. In a production fit these would come from a
**prior light-only fit** rather than being hardcoded; this tutorial uses hardcoded numbers to keep the script a single
self-contained search.
**Option B — JSON centres + hardcoded luminosity list (the original API, fine for short, fixed-length tutorials).**
Load the centres from `scaling_galaxies_centres.json` with `al.from_json` (the same loader used for the main and
extras tiers above) and define the luminosities as a Python list. Concise and obvious for small populations; awkward
once you have more than a handful.

There are two production patterns for obtaining the luminosities:
In a real analysis the luminosities come from a prior light-only fit. Two production patterns for obtaining them:

- **Standalone light-only fit.** Run a single-stage non-linear search whose model is just MGE bulges for every galaxy
(no mass, no source). After the fit, compute total luminosity per galaxy from the bulge gaussian parameters:
`total_luminosity = sum(2 * pi * sigma**2 / axis_ratio * intensity) / pixel_scale**2`. See the worked example at
`scripts/group/features/scaling_relation/modeling_for_luminosities.py`.
`total_luminosity = sum(2 * pi * sigma**2 / axis_ratio * intensity) / pixel_scale**2`. The standalone example at
`scripts/group/features/scaling_relation/modeling_for_luminosities.py` writes its result as a `scaling_galaxies.csv`
in the dataset folder, which can then be loaded directly via Option A here.

- **As the `source_lp[0]` stage of a SLaM pipeline.** Every group SLaM script defines a `source_lp_0` function whose
sole purpose is to fit a light-only MGE model to the main lens, extra galaxies and scaling galaxies in one go.
Expand All @@ -163,13 +170,27 @@
docstring. The luminosity computation lives in the `source_lp_1` function that consumes the `source_lp_result_0`
result.

The order of the list below must match `scaling_galaxies_centres`.
We use Option A by default below. The Option B equivalent is shown commented out — uncomment it (and comment out
Option A) to switch.
"""
scaling_galaxies_luminosity_list = [0.45, 0.45]

assert len(scaling_galaxies_luminosity_list) == len(list(scaling_galaxies_centres)), (
"Number of scaling-galaxy luminosities must match the number of scaling-galaxy centres."
# Option A: CSV (recommended)
scaling_galaxies_table = al.galaxy_table_from_csv(
file_path=dataset_path / "scaling_galaxies.csv"
)
scaling_galaxies_centres = scaling_galaxies_table.centres
scaling_galaxies_luminosity_list = scaling_galaxies_table.luminosities

# Option B: JSON centres + hardcoded luminosities (uncomment to use instead)
# scaling_galaxies_centres = al.from_json(
# file_path=dataset_path / "scaling_galaxies_centres.json"
# )
# scaling_galaxies_luminosity_list = [0.45, 0.45]
# assert len(scaling_galaxies_luminosity_list) == len(list(scaling_galaxies_centres)), (
# "Number of scaling-galaxy luminosities must match the number of scaling-galaxy centres."
# )

print(f"Scaling galaxies centres: {scaling_galaxies_centres}")
print(f"Scaling galaxies luminosities: {scaling_galaxies_luminosity_list}")

"""
__Main Lens Galaxies__
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@
# from autoconf import setup_notebook; setup_notebook()

import numpy as np
import json
from pathlib import Path
import autofit as af
import autolens as al
Expand Down Expand Up @@ -269,30 +268,34 @@ def total_luminosity_from(galaxy):
"""
__Output__

Write the luminosities to JSON next to the centre files so the scaling-relation modeling script can load them directly
in a follow-up run.
Write the centres + luminosities to a `scaling_galaxies.csv` next to the centre JSONs. This is the same file the
scaling-relation modeling script consumes via `al.galaxy_table_from_csv`, so the result of this fit can be chained
into the next step with no manual copy/paste.
"""
luminosities_path = dataset_path / "scaling_galaxies_luminosities.json"
with open(luminosities_path, "w") as f:
json.dump([float(l) for l in scaling_luminosities], f, indent=2)
csv_path = dataset_path / "scaling_galaxies.csv"

print(f"Wrote scaling-galaxy luminosities to: {luminosities_path}")
al.galaxy_table_to_csv(
centres=list(scaling_galaxies_centres),
luminosities=[float(l) for l in scaling_luminosities],
file_path=csv_path,
)

print(f"Wrote scaling-galaxy centres + luminosities to: {csv_path}")

"""
__Wrap Up__

The numbers printed above can be pasted into the `scaling_galaxies_luminosity_list` of `modeling.py` (or loaded from
`scaling_galaxies_luminosities.json`).

Two ways to chain into the next step:
The CSV at `dataset_path / "scaling_galaxies.csv"` is the canonical chain-point. The downstream modeling script
loads it directly:

- **Manual:** copy the printed list into `scripts/group/features/scaling_relation/modeling.py`'s
`scaling_galaxies_luminosity_list = [...]`.
table = al.galaxy_table_from_csv(file_path=dataset_path / "scaling_galaxies.csv")
scaling_galaxies_centres = table.centres
scaling_galaxies_luminosity_list = table.luminosities

- **Programmatic:** load `scaling_galaxies_luminosities.json` at the top of `modeling.py` via
`json.load(open(dataset_path / "scaling_galaxies_luminosities.json"))`. This pattern matches what the SLaM pipelines
do when they chain `source_lp[0]` -> `source_lp[1]`.
Re-running this script overwrites the CSV in place, so iterating on the light fit and re-running the lens fit is
just two `python ...` invocations.

For the chained-pipeline alternative, see `scripts/group/slam.py` (section `__SOURCE LP PIPELINE — stage 0__`) and
For the chained-pipeline alternative — where the light fit is the `source_lp[0]` stage of a SLaM run instead of a
standalone search — see `scripts/group/slam.py` (`__SOURCE LP PIPELINE — stage 0__`) and
`scripts/group/features/pixelization/slam.py`.
"""
25 changes: 25 additions & 0 deletions scripts/group/features/scaling_relation/simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,31 @@
file_path=Path(dataset_path, "scaling_galaxies_centres.json"),
)

"""
__Galaxy Population CSVs__

The modeling script loads luminosities (and centres) for both the extras and the scaling tier from CSVs written here.
The simulator knows the truth values of the per-galaxy luminosities so we write them out alongside the centre JSONs.

The CSV schema is `y, x, luminosity, redshift?` — see `al.galaxy_table_from_csv` /
`al.galaxy_table_to_csv` (`autogalaxy/galaxy/galaxy_table.py`). Centre JSONs above are kept for backward compatibility;
new consumers should prefer the CSVs.
"""
extra_galaxies_luminosities = [0.9, 0.9]
scaling_galaxies_luminosities = [0.45, 0.45]

al.galaxy_table_to_csv(
centres=extra_galaxies_centres,
luminosities=extra_galaxies_luminosities,
file_path=Path(dataset_path, "extra_galaxies.csv"),
)

al.galaxy_table_to_csv(
centres=scaling_galaxies_centres,
luminosities=scaling_galaxies_luminosities,
file_path=Path(dataset_path, "scaling_galaxies.csv"),
)

"""
__Positions__

Expand Down
60 changes: 44 additions & 16 deletions scripts/imaging/features/scaling_relation/modeling.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,10 @@
from that result to compute luminosities and bound / scale the mass models. See `scripts/group/slam.py`,
`scripts/group/features/pixelization/slam.py`, and the other group `slam.py` variants for production examples.

This tutorial uses **hardcoded luminosity lists** for readability — they would be the *output* of one of the patterns
above in production code.
This tutorial loads the luminosities from a `scaling_galaxies.csv` written by the simulator (see
`al.galaxy_table_from_csv` further down). In a real analysis the same CSV would be the *output* of one of the patterns
above — `modeling_for_luminosities.py` already writes its result in this format, and the SLAM `source_lp[0]` stage can
similarly emit one.

__Redshifts__

Expand Down Expand Up @@ -160,31 +162,57 @@
"""
__Centres__

Two JSON files. The naming convention used by the dataset (extras vs scaling) carries through here purely as a labelling
convenience — both populations end up in the same `extra_galaxies` collection in the model.
The individually-modelled tier loads its centres from a JSON file (a list of (y, x) tuples) — the centres are the only
input the modeling script needs for that tier.
"""
individual_extras_centres = al.from_json(
file_path=dataset_path / "extra_galaxies_centres.json"
)
relational_extras_centres = al.from_json(
file_path=dataset_path / "scaling_galaxies_centres.json"
)

print(f"Individually-modelled extras: {individual_extras_centres}")
print(f"Scaling-relation extras: {relational_extras_centres}")

"""
__Luminosities__
__Centres + Luminosities (scaling-relation tier)__

The scaling-relation tier needs a measured luminosity per galaxy. As discussed in the header, in production these come
from a prior light-only fit (see `modeling_for_luminosities.py` or the SLAM `source_lp[0]` stage). The order of this list
must match `relational_extras_centres`.
"""
relational_extras_luminosity_list = [0.45, 0.45]
The scaling-relation tier needs both centres AND a measured luminosity per galaxy. There are two equally-supported
ways to provide them in PyAutoLens — both shown below so you can pick whichever fits your workflow.

**Option A — CSV via `al.galaxy_table_from_csv` (recommended for non-trivial galaxy counts).** The simulator writes a
`scaling_galaxies.csv` with columns `y, x, luminosity` (and optional `redshift`) alongside the centre JSONs. We load it
in one call which returns a typed `GalaxyTable` with `.centres` (a `Grid2DIrregular`), `.luminosities`, and (optionally)
`.redshifts`. This scales naturally to populations of tens or hundreds of galaxies — the source of truth lives in a
single editable file.

In a real analysis, a prior light-only fit produces this CSV — see
`scripts/group/features/scaling_relation/modeling_for_luminosities.py` for the standalone version of that fit, or the
SLAM `source_lp[0]` stage in `scripts/group/slam.py` for the chained-pipeline equivalent.

assert len(relational_extras_luminosity_list) == len(list(relational_extras_centres)), (
"Number of luminosities must match number of scaling-relation extra galaxy centres."
**Option B — JSON centres + hardcoded luminosity list (the original API, fine for short, fixed-length tutorials).**
Load the centres from `scaling_galaxies_centres.json` with `al.from_json` (the same loader used for the
individually-modelled tier above) and define the luminosities as a Python list. Concise and obvious for small
populations; awkward once you have more than a handful.

We use Option A by default below. The Option B equivalent is shown commented out — uncomment it (and comment out
Option A) to switch.
"""
# Option A: CSV (recommended)
relational_extras_table = al.galaxy_table_from_csv(
file_path=dataset_path / "scaling_galaxies.csv"
)
relational_extras_centres = relational_extras_table.centres
relational_extras_luminosity_list = relational_extras_table.luminosities

# Option B: JSON centres + hardcoded luminosities (uncomment to use instead)
# relational_extras_centres = al.from_json(
# file_path=dataset_path / "scaling_galaxies_centres.json"
# )
# relational_extras_luminosity_list = [0.45, 0.45]
# assert len(relational_extras_luminosity_list) == len(list(relational_extras_centres)), (
# "Number of luminosities must match number of scaling-relation extra galaxy centres."
# )

print(f"Scaling-relation extras: {relational_extras_centres}")
print(f"Scaling-relation luminosities: {relational_extras_luminosity_list}")

"""
__Lens__
Expand Down
26 changes: 26 additions & 0 deletions scripts/imaging/features/scaling_relation/simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,32 @@
file_path=Path(dataset_path, "scaling_galaxies_centres.json"),
)

"""
__Galaxy Population CSVs__

The modeling script loads luminosities (and centres) for the scaling-relation tier from a CSV
written here. The simulator knows the truth values of the per-galaxy luminosities so we write
them out alongside the centre JSONs.

The CSV schema is `y, x, luminosity, redshift?` -- see `al.galaxy_table_from_csv` /
`al.galaxy_table_to_csv` (`autogalaxy/galaxy/galaxy_table.py`). Centre JSONs above are kept for
backward compatibility; new consumers should prefer the CSV.
"""
extra_galaxies_luminosities = [0.9, 0.8]
scaling_galaxies_luminosities = [0.45, 0.45]

al.galaxy_table_to_csv(
centres=extra_galaxies_centres,
luminosities=extra_galaxies_luminosities,
file_path=Path(dataset_path, "extra_galaxies.csv"),
)

al.galaxy_table_to_csv(
centres=scaling_galaxies_centres,
luminosities=scaling_galaxies_luminosities,
file_path=Path(dataset_path, "scaling_galaxies.csv"),
)

"""
Finished.
"""
Loading