Coverage for src/galsbi/ucat/plugins/apply_shear.py: 99%
90 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) 2019 ETH Zurich, Institute for Particle Physics and Astrophysics
3"""
4Created on Mar 27, 2019
5author: Joerg Herbel
6"""
8import h5py
9import healpy as hp
10import numpy as np
11from cosmic_toolbox import logger
12from ivy.plugin.base_plugin import BasePlugin
13from ufig import coordinate_util, io_util
15from galsbi.ucat import lensing_util
17LOGGER = logger.get_logger(__file__)
20def evaluate_healpix_shear_map(path, ra, dec):
21 """
22 Reads in a healpix map and evaluates it at given positions.
24 :param path: path where map is stored, assumes that file contains three maps (kappa,
25 gamma1, gamma2)
26 :param ra: right ascension where map is evaluated
27 :param dec: declinations where map is evaluated
28 :return: kappa, gamma1 and gamma2 evaluated at input positions
29 """
31 # read map
32 map_kappa, map_gamma_1, map_gamma_2 = hp.read_map(path, field=(0, 1, 2))
34 # get pixel indices
35 pixel_ind = coordinate_util.radec2pix(ra, dec, hp.npix2nside(map_kappa.size))
37 # read out pixels
38 kappa = map_kappa[pixel_ind]
39 gamma_1 = map_gamma_1[pixel_ind]
40 gamma_2 = map_gamma_2[pixel_ind]
42 return kappa, gamma_1, gamma_2
45def linear_interpolation(x0, y0, x1, y1, x):
46 """
47 Vectorized linear interpolation between two data points.
49 :param x0: x-coordinates of first data points
50 :param y0: y-coordinates of first data points
51 :param x1: x-coordinates of second data points
52 :param y1: y-coordinates of second data points
53 :param x: positions where interpolation is evaluated
54 :return: interpolated values
55 """
56 y = y0 + (x - x0) * (y1 - y0) / (x1 - x0)
57 return y
60def interpolate_maps_fast(z_maps, maps, z, pixel_ind, ind_intervals):
61 """
62 Linearly interpolate to input redshifts between healpix maps at given redshifts.
64 :param z_maps: redshifts of input maps, assumed to be ordered
65 :param maps: input maps corresponding to z_maps, type: hdf5-dataset or numpy-array
66 :param z: redshifts to which maps will be interpolated, assumed to be sorted
67 :param pixel_ind: indices indicating pixels of input maps to interpolate, same size
68 as z
69 :param ind_intervals: indices splitting up z into chunks that lie between the maps
70 :return: interpolated values
71 """
73 pixel_unique, indices_inv = np.unique(pixel_ind, return_inverse=True)
74 pixel_min, pixel_max = pixel_unique.min(), pixel_unique.max()
75 maps_slab = maps[:, pixel_min : pixel_max + 1] # hdf read-in with hyperslabs
76 maps_unique = maps_slab[:, pixel_unique - pixel_unique[0]]
77 maps_ind = maps_unique[:, indices_inv]
79 intpol_values = np.empty_like(z)
81 # galaxies at redshifts lower than the first map --> interpolate between zero shear
82 # at z=0 and first map
83 intpol_values[: ind_intervals[0]] = linear_interpolation(
84 0,
85 np.zeros(ind_intervals[0]),
86 z_maps[0],
87 maps_ind[0, : ind_intervals[0]],
88 z[: ind_intervals[0]],
89 )
91 # galaxies in between two maps
92 for i_map in range(len(ind_intervals) - 1):
93 ind_low = ind_intervals[i_map]
94 ind_up = ind_intervals[i_map + 1]
95 intpol_values[ind_low:ind_up] = linear_interpolation(
96 z_maps[i_map],
97 maps_ind[i_map, ind_low:ind_up],
98 z_maps[i_map + 1],
99 maps_ind[i_map + 1, ind_low:ind_up],
100 z[ind_low:ind_up],
101 )
103 # galaxies at redshifts higher than the last map --> use last map to set shear
104 # values
105 intpol_values[ind_intervals[-1] :] = maps_ind[-1, ind_intervals[-1] :]
107 return intpol_values
110def interpolate_maps(z_maps, maps, z, pixel_ind, ind_intervals):
111 """
112 Linearly interpolate to input redshifts between healpix maps at given redshifts.
114 :param z_maps: redshifts of input maps, assumed to be ordered
115 :param maps: input maps corresponding to z_maps, type: hdf5-dataset or numpy-array
116 :param z: redshifts to which maps will be interpolated, assumed to be sorted
117 :param pixel_ind: indices indicating pixels of input maps to interpolate, same size
118 as z
119 :param ind_intervals: indices splitting up z into chunks that lie between the maps
120 :return: interpolated values
121 """
123 intpol_values = np.empty_like(z)
124 map_low = np.empty(maps.shape[1], dtype=maps.dtype)
125 map_up = maps[0][...]
127 # galaxies at redshifts lower than the first map --> interpolate between zero shear
128 # at z=0 and first map
129 intpol_values[: ind_intervals[0]] = linear_interpolation(
130 0,
131 np.zeros(ind_intervals[0]),
132 z_maps[0],
133 map_up[pixel_ind[: ind_intervals[0]]],
134 z[: ind_intervals[0]],
135 )
137 # galaxies in between two maps
138 for i_map in range(len(ind_intervals) - 1):
139 ind_low = ind_intervals[i_map]
140 ind_up = ind_intervals[i_map + 1]
141 map_low[:] = map_up
142 map_up[:] = maps[i_map + 1][...]
144 intpol_values[ind_low:ind_up] = linear_interpolation(
145 z_maps[i_map],
146 map_low[pixel_ind[ind_low:ind_up]],
147 z_maps[i_map + 1],
148 map_up[pixel_ind[ind_low:ind_up]],
149 z[ind_low:ind_up],
150 )
152 # galaxies at redshifts higher than the last map --> use last map to set shear
153 # values
154 intpol_values[ind_intervals[-1] :] = map_up[pixel_ind[ind_intervals[-1] :]]
156 return intpol_values
159def evaluate_hdf5_shear_maps(path, ra, dec, z):
160 """
161 Evaluate hdf5 shear maps given at multiple redshifts for given redshifts and angular
162 positions.
164 :param path: path to hdf5 file containing shear maps; assumes that this file
165 contains four datasets:
166 - z: redshifts of maps
167 - kappa: kappa-maps
168 - gamma1: gamma1-maps
169 - gamma2: gamma2-maps
170 :param ra: right ascensions where maps will be evaluated
171 :param dec: declinations where maps will be evaluated
172 :param z: redshifts to which maps will be interpolated
173 :return: kappa, gamma1 and gamma2
174 """
176 # sort by redshift
177 ind_sort = np.argsort(z)
178 ra = ra[ind_sort]
179 dec = dec[ind_sort]
180 z = z[ind_sort]
182 with h5py.File(path, mode="r") as fh5:
183 # get redshifts of maps, assumed to be sorted
184 # print('{} reading shear map file {}'.format(str(datetime.now()), path))
185 LOGGER.info(f"reading shear map file {path}")
186 z_maps = fh5["z"][...]
188 # compute pixel indices
189 pixel_ind = coordinate_util.radec2pix(
190 ra, dec, hp.npix2nside(fh5["kappa"].shape[1])
191 )
193 # sort galaxies into redshift intervals between the maps
194 ind_intervals = np.searchsorted(z, z_maps)
196 # read out maps and interpolate
197 LOGGER.info(f"reading maps and interpolating for {len(z)} objects")
198 kappa = interpolate_maps_fast(z_maps, fh5["kappa"], z, pixel_ind, ind_intervals)
199 gamma1 = interpolate_maps_fast(
200 z_maps, fh5["gamma1"], z, pixel_ind, ind_intervals
201 )
202 gamma2 = interpolate_maps_fast(
203 z_maps, fh5["gamma2"], z, pixel_ind, ind_intervals
204 )
206 # undo sorting by redshift
207 ind_unsort = np.argsort(ind_sort)
208 kappa[:] = kappa[ind_unsort]
209 gamma1[:] = gamma1[ind_unsort]
210 gamma2[:] = gamma2[ind_unsort]
212 return kappa, gamma1, gamma2
215class Plugin(BasePlugin):
216 """
217 Apply a potentially redshift-dependent shear from input shear maps specified by
218 ctx.parameters.path_shear_map. Only first-order effects due to gamma are applied,
219 kappa is completely ignored.
220 There are three options:
222 1. The path is None, which will result in zero shear.
223 2. The path is a file readable by healpy. The file is then assumed to contain 3
224 healpix maps (kappa, gamma1, gamma2).
225 3. The path is and hdf5-file containing multiple, kappa-, gamma1- and gamma2-maps at
226 given redshifts. The shear values are computed by interpolating linearly between
227 the maps.
228 """
230 def __call__(self):
231 par = self.ctx.parameters
233 # first check if path of shear map is set to None --> zero shear
234 if par.path_shear_map is None:
235 self.ctx.galaxies.kappa = np.zeros(self.ctx.numgalaxies, dtype=float)
236 self.ctx.galaxies.gamma1 = np.zeros_like(self.ctx.galaxies.kappa)
237 self.ctx.galaxies.gamma2 = np.zeros_like(self.ctx.galaxies.kappa)
239 else:
240 ra, dec = coordinate_util.xy2radec(
241 coordinate_util.wcs_from_parameters(par),
242 self.ctx.galaxies.x,
243 self.ctx.galaxies.y,
244 )
246 path_shear_map = io_util.get_abs_path(
247 par.path_shear_map, root_path=par.maps_remote_dir
248 )
250 if h5py.is_hdf5(path_shear_map):
251 LOGGER.info(
252 "Shear map file is in hdf5-format, assuming multiple"
253 " shear maps at different redshifts"
254 )
255 (
256 self.ctx.galaxies.kappa,
257 self.ctx.galaxies.gamma1,
258 self.ctx.galaxies.gamma2,
259 ) = evaluate_hdf5_shear_maps(
260 path=path_shear_map, ra=ra, dec=dec, z=self.ctx.galaxies.z
261 )
262 else:
263 LOGGER.info(
264 "Shear map file is not in hdf5-format, assuming a single"
265 " shear map stored in fits-format"
266 )
267 (
268 self.ctx.galaxies.kappa,
269 self.ctx.galaxies.gamma1,
270 self.ctx.galaxies.gamma2,
271 ) = evaluate_healpix_shear_map(path=path_shear_map, ra=ra, dec=dec)
273 # apply sign to gamma1
274 self.ctx.galaxies.gamma1 *= par.gamma1_sign
276 # scale size
277 self.ctx.galaxies.r50 = lensing_util.calculate_size_magnification(
278 r=self.ctx.galaxies.int_r50, kappa=self.ctx.galaxies.kappa
279 )
280 # convert to arcsec
281 self.ctx.galaxies.r50_arcsec = self.ctx.galaxies.r50 * par.pixscale
283 # shear ellipticities
284 LOGGER.info("applying shear and magnification")
285 (
286 self.ctx.galaxies.e1,
287 self.ctx.galaxies.e2,
288 ) = lensing_util.apply_shear_to_ellipticities(
289 self.ctx.galaxies.int_e1,
290 self.ctx.galaxies.int_e2,
291 self.ctx.galaxies.kappa,
292 self.ctx.galaxies.gamma1,
293 self.ctx.galaxies.gamma2,
294 )
296 # magnify flux
298 # magnification in terms of flux, see Bartelmann & Schneider 2001,
299 # https://arxiv.org/pdf/astro-ph/9912508.pdf, eq. (3.14)
300 magnification = lensing_util.calculate_flux_magnification(
301 self.ctx.galaxies.kappa, self.ctx.galaxies.gamma1, self.ctx.galaxies.gamma2
302 )
304 # in terms of magnitudes
305 magnification[:] = 2.5 * np.log10(magnification)
307 for mag in self.ctx.galaxies.magnitude_dict.values():
308 mag -= magnification
310 # add new columns to list
311 self.ctx.galaxies.columns.extend(
312 ["gamma1", "gamma2", "kappa", "e1", "e2", "r50", "r50_arcsec"]
313 )
315 def __str__(self):
316 return "apply shear"