99wiring can reuse it without code duplication.
1010
1111User-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"""
1720import logging
1821from typing import Callable , Dict , List , Optional
2730
2831logger = 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
40113def 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):
60139def 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
144231LATENT_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 ,
0 commit comments