Coverage for src/galsbi/ucat/magnitude_calculator.py: 95%
65 statements
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-13 03:24 +0000
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-13 03:24 +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 = 3 * 1e8 * 1e6 # speed of light, units: micrometre/s
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.
42 """
44 def __init__(self, filters, sed_templates):
45 self.filters = filters
46 self.sed_templates = sed_templates
47 self.extinction_spline = spectrum_util.spline_ext_coeff()
48 self.n_templates = sed_templates["amp"].shape[0]
50 def __call__(
51 self, redshifts, excess_b_v, coeffs, filter_names, return_fluxes=False
52 ):
53 # check if iterable
54 from collections.abc import Iterable
56 if not isinstance(filter_names, Iterable):
57 filter_names = [filter_names]
59 n_obj = redshifts.size
60 fluxes = {f: np.empty(n_obj) for f in filter_names}
62 for i in LOGGER.progressbar(
63 range(n_obj), desc="getting magnitudes", at_level="debug"
64 ):
65 lam_obs = self.sed_templates["lam"] * (1 + redshifts[i])
67 spec = spectrum_util.construct_reddened_spectrum(
68 lam_obs=lam_obs,
69 templates_amp=self.sed_templates["amp"],
70 coeff=coeffs[i],
71 excess_b_v=excess_b_v[i],
72 extinction_spline=self.extinction_spline,
73 )
75 for f in filter_names:
76 filt_amp = np.interp(
77 lam_obs, self.filters[f]["lam"], self.filters[f]["amp"]
78 )
79 fluxes[f][i] = np.trapz(lam_obs * spec * filt_amp, x=lam_obs).item() / (
80 speed_of_light_c * self.filters[f]["integ"]
81 )
83 if return_fluxes:
84 return fluxes
86 else:
87 mags = {}
88 for f in filter_names:
89 mags[f] = flux_to_AB_mag(fluxes[f])
90 mags[f][np.isnan(mags[f])] = np.inf
91 del fluxes[f]
93 return mags
96class MagCalculatorTable:
97 """
98 Computes galaxy magnitudes by looking up pre-computed values of the integrals of our
99 template spectra as a function of redshift and extinction E(B-V). The integrals need
100 to be pre-computed for every filter band separately, such that for every filter
101 band, we have five (number of template spectra) 2-dim. tables of integrals with
102 redshift on the one and E(B-V) on the other axis. See also
103 docs/jupyter_notebooks/tabulate_template_integrals.ipynb and
104 docs/jupyter_notebooks/extinction.ipynb.
105 """
107 def __init__(
108 self,
109 filter_names,
110 filepath_sed_integrals,
111 reload_cache=False,
112 copy_to_cwd=False,
113 ):
114 # TODO: check if this cache helped at all and if it did, fix the memory leak
115 """
116 if reload_cache:
117 for key in TEMPL_INTEGRALS_CACHE.keys():
118 del TEMPL_INTEGRALS_CACHE[key]
119 sed_templates_util.load_sed_integrals(
120 filepath_sed_integrals,
121 filter_names,
122 crop_negative=True,
123 sed_templates=TEMPL_INTEGRALS_CACHE,
124 )
125 self.templates_int_table_dict = TEMPL_INTEGRALS_CACHE
126 self.z_grid = TEMPL_INTEGRALS_CACHE.z_grid
127 self.excess_b_v_grid = TEMPL_INTEGRALS_CACHE.excess_b_v_grid
129 """
130 self.templates_int_table_dict = sed_templates_util.load_sed_integrals(
131 filepath_sed_integrals,
132 filter_names,
133 crop_negative=True,
134 sed_templates=None,
135 copy_to_cwd=copy_to_cwd,
136 )
137 self.z_grid = self.templates_int_table_dict.z_grid
138 self.excess_b_v_grid = self.templates_int_table_dict.excess_b_v_grid
140 self.n_templates = self.templates_int_table_dict.n_templates
142 def __call__(
143 self, redshifts, excess_b_v, coeffs, filter_names, return_fluxes=False
144 ):
145 # check if iterable
146 from collections.abc import Iterable
148 if not isinstance(filter_names, Iterable):
149 filter_names = [filter_names]
151 # find the values of redshift and extinction in the grid for all objects
152 z_ind = find_closest_ind(self.z_grid, redshifts)
153 excess_b_v_ind = find_closest_ind(self.excess_b_v_grid, excess_b_v)
155 fluxes = {}
157 for f in filter_names:
158 templates_int_tables = self.templates_int_table_dict[f]
159 fluxes[f] = np.zeros(redshifts.size, dtype=np.float64)
161 # create fluxes from templates
162 for i in range(coeffs.shape[1]):
163 fluxes[f] += (
164 coeffs[:, i] * templates_int_tables[i][z_ind, excess_b_v_ind]
165 )
167 if return_fluxes:
168 return fluxes
169 else:
170 # convert flux to AB mag
171 magnitudes = {}
172 for f in filter_names:
173 magnitudes[f] = flux_to_AB_mag(fluxes[f])
174 del fluxes[f]
175 return magnitudes