Skip to content

Commit 0f67334

Browse files
authored
Merge pull request #557 from PyAutoLabs/feature/flux-latents-raw
fix: raw-flux latents + soft-fail magzero-required µJy
2 parents 1ec7390 + dd3d09b commit 0f67334

4 files changed

Lines changed: 268 additions & 43 deletions

File tree

autolens/analysis/latent.py

Lines changed: 105 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,13 @@
99
wiring can reuse it without code duplication.
1010
1111
User-level enable/disable: each key in ``autolens/config/latent.yaml`` maps
12-
to a bool. All five default ``false`` because ``compute_latent_samples``
13-
runs on every fit (``latent_after_fit: true`` in autofit's default
14-
``output.yaml``) and the latents that require ``magzero`` would otherwise
15-
crash existing fits where ``magzero`` is not passed.
12+
to a bool. The raw-flux latents (``total_lens_flux``,
13+
``total_lensed_source_flux``, ``total_source_flux``) require no instrument
14+
inputs and default ``true``. The microjansky variants require ``magzero``
15+
on the Analysis; they default ``false`` and return NaN with a single
16+
warning per process if enabled without ``magzero`` (rather than raising,
17+
which would kill the post-fit metric write of an otherwise-converged
18+
search).
1619
"""
1720
import logging
1821
from typing import Callable, Dict, List, Optional
@@ -27,25 +30,101 @@
2730

2831
logger = logging.getLogger(__name__)
2932

33+
# Latent names that have already emitted a "magzero missing" warning in this
34+
# process. Used by ``_maybe_magzero_warn`` to deduplicate the message across
35+
# the many fit evaluations a single search performs.
36+
_MAGZERO_WARNED: set = set()
3037

31-
def _require_magzero(magzero, name):
38+
39+
def _maybe_magzero_warn(magzero, name) -> bool:
40+
"""
41+
Return True when ``magzero`` is missing (and emit a one-time-per-process
42+
warning for ``name``); False otherwise.
43+
44+
Callers that get True must early-return ``xp.nan`` — the µJy conversion
45+
is meaningless without a zero-point, but a search-killing raise here
46+
would discard otherwise-converged fits.
47+
"""
3248
if magzero is None:
33-
raise ValueError(
34-
f"magzero must be passed to the Analysis via kwargs to compute "
35-
f"the '{name}' latent. Disable it in config/latent.yaml or "
36-
f"pass magzero=<value>."
49+
if name not in _MAGZERO_WARNED:
50+
logger.warning(
51+
"magzero not set on Analysis; '%s' latent will be NaN. "
52+
"Pass magzero=<value> to AnalysisImaging to enable it, "
53+
"or disable in config/latent.yaml to silence this warning.",
54+
name,
55+
)
56+
_MAGZERO_WARNED.add(name)
57+
return True
58+
return False
59+
60+
61+
def total_lens_flux(fit, magzero=None, xp=np):
62+
"""
63+
Total integrated flux of the lens galaxy (``fit.tracer.galaxies[0]``),
64+
in the raw image units the fit was performed in.
65+
66+
Requires no instrument inputs — ``magzero`` is accepted for uniform
67+
dispatcher context but ignored. See the workspace flux guide
68+
(``scripts/guides/units/flux.py``) for how to convert to microjanskies.
69+
70+
Returns NaN when galaxy 0 has no light profile.
71+
"""
72+
try:
73+
image = fit.galaxy_image_dict[fit.tracer.galaxies[0]]
74+
except (AttributeError, KeyError, IndexError):
75+
return xp.nan
76+
return xp.sum(image.array)
77+
78+
79+
def total_lensed_source_flux(fit, magzero=None, xp=np):
80+
"""
81+
Image-plane integrated flux of the source galaxy after lensing
82+
(``fit.galaxy_image_dict[fit.tracer.galaxies[-1]]``), in raw image
83+
units. ``magzero`` is accepted but ignored.
84+
"""
85+
try:
86+
image = fit.galaxy_image_dict[fit.tracer.galaxies[-1]]
87+
except (AttributeError, KeyError, IndexError):
88+
return xp.nan
89+
return xp.sum(image.array)
90+
91+
92+
def total_source_flux(fit, magzero=None, xp=np):
93+
"""
94+
Source-plane intrinsic flux of the source galaxy, in raw image units.
95+
96+
Reads from ``fit.tracer_linear_light_profiles_to_light_profiles`` so
97+
that linear light profiles (whose ``intensity`` is solved by the
98+
inversion) contribute the correct image — same tracer-conversion
99+
handling as :func:`total_source_flux_mujy`.
100+
101+
``magzero`` is accepted but ignored.
102+
"""
103+
try:
104+
tracer = fit.tracer_linear_light_profiles_to_light_profiles
105+
source_image = tracer.galaxies[-1].image_2d_from(
106+
grid=fit.dataset.grids.lp, xp=xp
37107
)
108+
except (AttributeError, IndexError):
109+
return xp.nan
110+
return xp.sum(source_image.array)
38111

39112

40113
def total_lens_flux_mujy(fit, magzero, xp=np):
41114
"""
42115
Total integrated flux of the lens galaxy (``fit.tracer.galaxies[0]``),
43116
magzero-converted to microjanskies.
44117
45-
Returns NaN when galaxy 0 has no light profile (raises ``KeyError`` /
46-
``AttributeError`` inside ``fit.galaxy_image_dict``).
118+
Returns NaN — with a one-time-per-process warning — when ``magzero``
119+
is missing, rather than raising. The µJy conversion is meaningless
120+
without a zero-point, but a hard raise during post-fit latent
121+
computation would discard the result of an otherwise-converged
122+
multi-hour search.
123+
124+
Also returns NaN when galaxy 0 has no light profile.
47125
"""
48-
_require_magzero(magzero, "total_lens_flux_mujy")
126+
if _maybe_magzero_warn(magzero, "total_lens_flux_mujy"):
127+
return xp.nan
49128
try:
50129
image = fit.galaxy_image_dict[fit.tracer.galaxies[0]]
51130
except (AttributeError, KeyError, IndexError):
@@ -60,9 +139,13 @@ def total_lens_flux_mujy(fit, magzero, xp=np):
60139
def total_lensed_source_flux_mujy(fit, magzero, xp=np):
61140
"""
62141
Image-plane integrated flux of the source galaxy after lensing
63-
(``fit.galaxy_image_dict[fit.tracer.galaxies[-1]]``).
142+
(``fit.galaxy_image_dict[fit.tracer.galaxies[-1]]``), in microjanskies.
143+
144+
Returns NaN + one warning when ``magzero`` is missing; see
145+
:func:`total_lens_flux_mujy` for the rationale.
64146
"""
65-
_require_magzero(magzero, "total_lensed_source_flux_mujy")
147+
if _maybe_magzero_warn(magzero, "total_lensed_source_flux_mujy"):
148+
return xp.nan
66149
try:
67150
image = fit.galaxy_image_dict[fit.tracer.galaxies[-1]]
68151
except (AttributeError, KeyError, IndexError):
@@ -83,8 +166,12 @@ def total_source_flux_mujy(fit, magzero, xp=np):
83166
is solved by the inversion at fit time) contribute the correct image.
84167
For non-linear fits this property is a no-op pass-through (returns
85168
``fit.tracer``), so the numpy-only and JAX paths both work uniformly.
169+
170+
Returns NaN + one warning when ``magzero`` is missing; see
171+
:func:`total_lens_flux_mujy` for the rationale.
86172
"""
87-
_require_magzero(magzero, "total_source_flux_mujy")
173+
if _maybe_magzero_warn(magzero, "total_source_flux_mujy"):
174+
return xp.nan
88175
try:
89176
tracer = fit.tracer_linear_light_profiles_to_light_profiles
90177
source_image = tracer.galaxies[-1].image_2d_from(
@@ -142,6 +229,9 @@ def effective_einstein_radius(fit, magzero, xp=np):
142229

143230

144231
LATENT_FUNCTIONS: Dict[str, Callable] = {
232+
"total_lens_flux": total_lens_flux,
233+
"total_lensed_source_flux": total_lensed_source_flux,
234+
"total_source_flux": total_source_flux,
145235
"total_lens_flux_mujy": total_lens_flux_mujy,
146236
"total_lensed_source_flux_mujy": total_lensed_source_flux_mujy,
147237
"total_source_flux_mujy": total_source_flux_mujy,

autolens/config/latent.yaml

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,35 @@
99
# Workspaces should mirror this file in their own `config/latent.yaml` to
1010
# override defaults locally (workspace values shadow library values).
1111
#
12-
# All keys ship `false` because:
13-
# - `compute_latent_samples` runs on every fit (`latent_after_fit: true`
14-
# in autofit's default output.yaml), so on-by-default would crash any
15-
# existing fit that doesn't pass `magzero` to the Analysis.
16-
# - autoconf lowercases yaml keys at read time, so the registry/yaml
17-
# names must be snake_case-lowercase (this leaks into the `latent.csv`
18-
# column header — e.g. `total_lens_flux_mujy`, not `_muJy`).
12+
# Raw-flux keys (`total_lens_flux`, `total_lensed_source_flux`,
13+
# `total_source_flux`) require no instrument inputs and default `true`.
14+
# The `_mujy` variants require `magzero` on the Analysis; they default
15+
# `false` and return NaN + one warning per process if enabled without
16+
# `magzero` (rather than raising, which would discard a converged search).
17+
#
18+
# autoconf lowercases yaml keys at read time, so the registry/yaml names
19+
# must be snake_case-lowercase (this leaks into the `latent.csv` column
20+
# header — e.g. `total_lens_flux_mujy`, not `_muJy`).
21+
22+
# `total_lens_flux` — total integrated flux of the lens galaxy
23+
# (`fit.tracer.galaxies[0]`), in the raw image units the fit was performed
24+
# in. No instrument inputs required.
25+
total_lens_flux: true
26+
27+
# `total_lensed_source_flux` — image-plane integrated flux of the source
28+
# galaxy after lensing (`fit.galaxy_image_dict[tracer.galaxies[-1]]`), in
29+
# raw image units.
30+
total_lensed_source_flux: true
31+
32+
# `total_source_flux` — source-plane intrinsic flux of the source galaxy,
33+
# in raw image units. Reads from
34+
# `tracer_linear_light_profiles_to_light_profiles` so linear-profile fits
35+
# get the correct (inversion-solved) flux.
36+
total_source_flux: true
1937

2038
# `total_lens_flux_mujy` — total integrated flux of the lens galaxy
2139
# (`fit.tracer.galaxies[0]`) in microjanskies. Requires `magzero` via
22-
# Analysis kwargs. Returns NaN when the lens has no light profile.
40+
# Analysis kwargs. Returns NaN + one warning if `magzero` is missing.
2341
total_lens_flux_mujy: false
2442

2543
# `total_lensed_source_flux_mujy` — image-plane integrated flux of the
@@ -34,6 +52,11 @@ total_source_flux_mujy: false
3452

3553
# `magnification` — ratio of image-plane lensed source flux to source-plane
3654
# intrinsic source flux. Dimensionless; `magzero` is accepted but unused.
55+
# Default `false` because it routes through the `_mujy` latents internally
56+
# (the µJy conversions cancel in the ratio) — to flip on, also flip on
57+
# `total_lensed_source_flux_mujy` and `total_source_flux_mujy` and supply
58+
# a `magzero`. A follow-up could rewire `magnification` to the raw-flux
59+
# latents so it's universally enable-able.
3760
magnification: false
3861

3962
# `effective_einstein_radius` — effective Einstein radius in arcseconds,

0 commit comments

Comments
 (0)