Coverage for src/galsbi/ucat/magnitude_calculator.py: 97%
65 statements
« prev ^ index » next coverage.py v7.10.4, created at 2025-08-18 15:15 +0000
« prev ^ index » next coverage.py v7.10.4, created at 2025-08-18 15:15 +0000
1# Copyright (C) 2018 ETH Zurich, Institute for Particle Physics and Astrophysics
3"""
4Created on Sept 2021
5author: Tomasz Kacprzak
6Code author: Joerg Herbel
7"""
9from collections import OrderedDict
11import numpy as np
12from cosmic_toolbox import logger
14from galsbi.ucat import sed_templates_util, spectrum_util
15from galsbi.ucat.galaxy_population_models.galaxy_luminosity_function import (
16 find_closest_ind,
17)
19LOGGER = logger.get_logger(__file__)
21speed_of_light_c_in_micrometer_per_sec = 3 * 1e8 * 1e6
23TEMPL_INTEGRALS_CACHE = OrderedDict()
26def flux_to_AB_mag(flux):
27 mag = -2.5 * np.log10(flux) - 48.6
28 mag[np.isnan(mag)] = np.inf
29 return mag
32def AB_mag_to_flux(mag):
33 return 10 ** ((mag + 48.6) / (-2.5))
36class MagCalculatorDirect:
37 """
38 Computes galaxy magnitudes by integrating redshifted and reddened galaxy spectra
39 which are given by a sum of five template spectra. See also
40 docs/jupyter_notebooks/spectra_to_magnitudes.ipynb and
41 docs/jupyter_notebooks/extinction.ipynb.
43 We use the following units:
45 - Wavelength: micrometer
46 - SED: erg/s/m2/Å
47 """
49 def __init__(self, filters, sed_templates):
50 self.filters = filters
51 self.sed_templates = sed_templates
52 self.extinction_spline = spectrum_util.spline_ext_coeff()
53 self.n_templates = sed_templates["amp"].shape[0]
55 def __call__(
56 self, redshifts, excess_b_v, coeffs, filter_names, return_fluxes=False
57 ):
58 # check if iterable
59 from collections.abc import Iterable
61 if not isinstance(filter_names, Iterable):
62 filter_names = [filter_names]
64 n_obj = redshifts.size
65 fluxes = {f: np.empty(n_obj) for f in filter_names}
67 for i in LOGGER.progressbar(
68 range(n_obj), desc="getting magnitudes", at_level="debug"
69 ):
70 lam_obs_in_micrometer = self.sed_templates["lam"] * (1 + redshifts[i])
72 spec = spectrum_util.construct_reddened_spectrum(
73 lam_obs=lam_obs_in_micrometer,
74 templates_amp=self.sed_templates["amp"], # in erg/s/m2/Å
75 coeff=coeffs[i],
76 excess_b_v=excess_b_v[i],
77 extinction_spline=self.extinction_spline,
78 )
80 for f in filter_names:
81 filt_amp = np.interp(
82 lam_obs_in_micrometer,
83 self.filters[f]["lam"],
84 self.filters[f]["amp"],
85 )
86 fluxes[f][i] = np.trapz(
87 lam_obs_in_micrometer * spec * filt_amp, x=lam_obs_in_micrometer
88 ).item() / (
89 speed_of_light_c_in_micrometer_per_sec * self.filters[f]["integ"]
90 )
92 if return_fluxes:
93 return fluxes
95 else:
96 mags = {}
97 for f in filter_names:
98 mags[f] = flux_to_AB_mag(fluxes[f])
99 mags[f][np.isnan(mags[f])] = np.inf
100 del fluxes[f]
102 return mags
105class MagCalculatorTable:
106 """
107 Computes galaxy magnitudes by looking up pre-computed values of the integrals of our
108 template spectra as a function of redshift and extinction E(B-V). The integrals need
109 to be pre-computed for every filter band separately, such that for every filter
110 band, we have five (number of template spectra) 2-dim. tables of integrals with
111 redshift on the one and E(B-V) on the other axis. See also
112 docs/jupyter_notebooks/tabulate_template_integrals.ipynb and
113 docs/jupyter_notebooks/extinction.ipynb.
114 """
116 def __init__(
117 self,
118 filter_names,
119 filepath_sed_integrals,
120 reload_cache=False,
121 copy_to_cwd=False,
122 ):
123 # TODO: check if this cache helped at all and if it did, fix the memory leak
124 """
125 if reload_cache:
126 for key in TEMPL_INTEGRALS_CACHE:
127 del TEMPL_INTEGRALS_CACHE[key]
128 sed_templates_util.load_sed_integrals(
129 filepath_sed_integrals,
130 filter_names,
131 crop_negative=True,
132 sed_templates=TEMPL_INTEGRALS_CACHE,
133 )
134 self.templates_int_table_dict = TEMPL_INTEGRALS_CACHE
135 self.z_grid = TEMPL_INTEGRALS_CACHE.z_grid
136 self.excess_b_v_grid = TEMPL_INTEGRALS_CACHE.excess_b_v_grid
138 """
139 self.templates_int_table_dict = sed_templates_util.load_sed_integrals(
140 filepath_sed_integrals,
141 filter_names,
142 crop_negative=True,
143 sed_templates=None,
144 copy_to_cwd=copy_to_cwd,
145 )
146 self.z_grid = self.templates_int_table_dict.z_grid
147 self.excess_b_v_grid = self.templates_int_table_dict.excess_b_v_grid
149 self.n_templates = self.templates_int_table_dict.n_templates
151 def __call__(
152 self, redshifts, excess_b_v, coeffs, filter_names, return_fluxes=False
153 ):
154 # check if iterable
155 from collections.abc import Iterable
157 if not isinstance(filter_names, Iterable):
158 filter_names = [filter_names]
160 # find the values of redshift and extinction in the grid for all objects
161 z_ind = find_closest_ind(self.z_grid, redshifts)
162 excess_b_v_ind = find_closest_ind(self.excess_b_v_grid, excess_b_v)
164 fluxes = {}
166 for f in filter_names:
167 templates_int_tables = self.templates_int_table_dict[f]
168 fluxes[f] = np.zeros(redshifts.size, dtype=np.float64)
170 # create fluxes from templates
171 for i in range(coeffs.shape[1]):
172 fluxes[f] += (
173 coeffs[:, i] * templates_int_tables[i][z_ind, excess_b_v_ind]
174 )
176 if return_fluxes:
177 return fluxes
178 else:
179 # convert flux to AB mag
180 magnitudes = {}
181 for f in filter_names:
182 magnitudes[f] = flux_to_AB_mag(fluxes[f])
183 del fluxes[f]
184 return magnitudes