def test_update_psf(self): fwhm = 0.3 sigma = util.fwhm2sigma(fwhm) x_grid, y_grid = util.make_grid(numPix=31, deltapix=0.05) from lenstronomy.LightModel.Profiles.gaussian import Gaussian gaussian = Gaussian() kernel_point_source = gaussian.function(x_grid, y_grid, amp=1., sigma_x=sigma, sigma_y=sigma, center_x=0, center_y=0) kernel_point_source /= np.sum(kernel_point_source) kernel_point_source = util.array2image(kernel_point_source) kwargs_psf = { 'psf_type': 'PIXEL', 'kernel_point_source': kernel_point_source } kwargs_psf_return, improved_bool = self.psf_fitting.update_psf( kwargs_psf, self.kwargs_lens, self.kwargs_source, self.kwargs_lens_light, self.kwargs_ps, factor=0.1, symmetry=1) assert improved_bool kernel_new = kwargs_psf_return['kernel_point_source'] kernel_true = self.kwargs_psf['kernel_point_source'] kernel_old = kwargs_psf['kernel_point_source'] diff_old = np.sum((kernel_old - kernel_true)**2) diff_new = np.sum((kernel_new - kernel_true)**2) assert diff_old > diff_new
def test_update_iterative(self): fwhm = 0.5 sigma = util.fwhm2sigma(fwhm) x_grid, y_grid = util.make_grid(numPix=31, deltapix=0.05) from lenstronomy.LightModel.Profiles.gaussian import Gaussian gaussian = Gaussian() kernel_point_source = gaussian.function(x_grid, y_grid, amp=1., sigma_x=sigma, sigma_y=sigma, center_x=0, center_y=0) kernel_point_source /= np.sum(kernel_point_source) kernel_point_source = util.array2image(kernel_point_source) kwargs_psf = {'psf_type': 'PIXEL', 'kernel_point_source': kernel_point_source} kwargs_psf_iter = {'stacking_method': 'median'} kwargs_psf_new = self.psf_fitting.update_iterative(kwargs_psf, self.kwargs_lens, self.kwargs_source, self.kwargs_lens_light, self.kwargs_ps, **kwargs_psf_iter) kernel_new = kwargs_psf_new['kernel_point_source'] kernel_true = self.kwargs_psf['kernel_point_source'] kernel_old = kwargs_psf['kernel_point_source'] diff_old = np.sum((kernel_old - kernel_true) ** 2) diff_new = np.sum((kernel_new - kernel_true) ** 2) assert diff_old > diff_new assert diff_new < 0.01 kwargs_psf_new = self.psf_fitting.update_iterative(kwargs_psf, self.kwargs_lens, self.kwargs_source, self.kwargs_lens_light, self.kwargs_ps, num_iter=3, no_break=True) kernel_new = kwargs_psf_new['kernel_point_source'] kernel_true = self.kwargs_psf['kernel_point_source'] kernel_old = kwargs_psf['kernel_point_source'] diff_old = np.sum((kernel_old - kernel_true) ** 2) diff_new = np.sum((kernel_new - kernel_true) ** 2) assert diff_old > diff_new assert diff_new < 0.01
def test_update_psf(self): fwhm = 0.5 sigma = util.fwhm2sigma(fwhm) x_grid, y_grid = util.make_grid(numPix=31, deltapix=0.05) from lenstronomy.LightModel.Profiles.gaussian import Gaussian gaussian = Gaussian() kernel_point_source = gaussian.function(x_grid, y_grid, amp=1., sigma=sigma, center_x=0, center_y=0) kernel_point_source /= np.sum(kernel_point_source) kernel_point_source = util.array2image(kernel_point_source) kwargs_psf = { 'psf_type': 'PIXEL', 'kernel_point_source': kernel_point_source } kwargs_psf_iter = {'stacking_method': 'median'} #mag = np.ones_like(x_pos) kwargs_psf_return, improved_bool, error_map = self.psf_fitting.update_psf( kwargs_psf, self.kwargs_params, **kwargs_psf_iter) assert improved_bool kernel_new = kwargs_psf_return['kernel_point_source'] kernel_true = self.kwargs_psf['kernel_point_source'] kernel_old = kwargs_psf['kernel_point_source'] diff_old = np.sum((kernel_old - kernel_true)**2) diff_new = np.sum((kernel_new - kernel_true)**2) assert diff_old > diff_new
def psf_configure(self, psf_type="GAUSSIAN", fwhm=1, kernelsize=11, deltaPix=1, truncate=6, kernel=None): """ :param psf_type: :param fwhm: :param pixel_grid: :return: """ # psf_type: 'NONE', 'gaussian', 'pixel' # 'pixel': kernel, kernel_large # 'gaussian': 'sigma', 'truncate' if psf_type == 'GAUSSIAN': sigma = util.fwhm2sigma(fwhm) sigma_axis = sigma x_grid, y_grid = util.make_grid(kernelsize, deltaPix) kernel_large = self.gaussian.function(x_grid, y_grid, amp=1., sigma_x=sigma_axis, sigma_y=sigma_axis, center_x=0, center_y=0) kernel_large /= np.sum(kernel_large) kernel_large = util.array2image(kernel_large) kernel_pixel = kernel_util.pixel_kernel(kernel_large) kwargs_psf = {'psf_type': psf_type, 'fwhm': fwhm, 'truncation': truncate*fwhm, 'kernel_point_source': kernel_large, 'kernel_pixel': kernel_pixel, 'pixel_size': deltaPix} elif psf_type == 'PIXEL': kernel_large = copy.deepcopy(kernel) kernel_large = kernel_util.cut_psf(kernel_large, psf_size=kernelsize) kernel_small = copy.deepcopy(kernel) kernel_small = kernel_util.cut_psf(kernel_small, psf_size=kernelsize) kwargs_psf = {'psf_type': "PIXEL", 'kernel_pixel': kernel_small, 'kernel_point_source': kernel_large} elif psf_type == 'NONE': kwargs_psf = {'psf_type': 'NONE'} else: raise ValueError("psf type %s not supported!" % psf_type) psf_class = PSF(kwargs_psf) return psf_class
def test_update_iterative(self): fwhm = 0.5 sigma = util.fwhm2sigma(fwhm) x_grid, y_grid = util.make_grid(numPix=31, deltapix=0.05) from lenstronomy.LightModel.Profiles.gaussian import Gaussian gaussian = Gaussian() kernel_point_source = gaussian.function(x_grid, y_grid, amp=1., sigma=sigma, center_x=0, center_y=0) kernel_point_source /= np.sum(kernel_point_source) kernel_point_source = util.array2image(kernel_point_source) kwargs_psf = { 'psf_type': 'PIXEL', 'kernel_point_source': kernel_point_source, 'kernel_point_source_init': kernel_point_source } kwargs_psf_iter = { 'stacking_method': 'median', 'psf_symmetry': 2, 'psf_iter_factor': 0.2, 'block_center_neighbour': 0.1, 'error_map_radius': 0.5, 'new_procedure': False, 'no_break': False, 'verbose': True, 'keep_psf_error_map': False } kwargs_params = copy.deepcopy(self.kwargs_params) kwargs_ps = kwargs_params['kwargs_ps'] del kwargs_ps[0]['source_amp'] print(kwargs_params['kwargs_ps']) kwargs_psf_new = self.psf_fitting.update_iterative( kwargs_psf, kwargs_params, **kwargs_psf_iter) kernel_new = kwargs_psf_new['kernel_point_source'] kernel_true = self.kwargs_psf['kernel_point_source'] kernel_old = kwargs_psf['kernel_point_source'] diff_old = np.sum((kernel_old - kernel_true)**2) diff_new = np.sum((kernel_new - kernel_true)**2) assert diff_old > diff_new assert diff_new < 0.01 assert 'psf_error_map' in kwargs_psf_new kwargs_psf_new = self.psf_fitting.update_iterative( kwargs_psf, kwargs_params, num_iter=3, no_break=True, keep_psf_error_map=True) kernel_new = kwargs_psf_new['kernel_point_source'] kernel_true = self.kwargs_psf['kernel_point_source'] kernel_old = kwargs_psf['kernel_point_source'] diff_old = np.sum((kernel_old - kernel_true)**2) diff_new = np.sum((kernel_new - kernel_true)**2) assert diff_old > diff_new assert diff_new < 0.01
def __init__(self, kernel, truncation=4): """ :param kernel: 2d kernel :param truncation: sigma scaling of kernel truncation """ fwhm = kernel_util.fwhm_kernel(kernel) self._sigma = util.fwhm2sigma(fwhm) self._truncation = truncation
def kernel_gaussian(kernel_numPix, deltaPix, fwhm): sigma = util.fwhm2sigma(fwhm) #if kernel_numPix % 2 == 0: # kernel_numPix += 1 x_grid, y_grid = util.make_grid(kernel_numPix, deltaPix) gaussian = Gaussian() kernel = gaussian.function(x_grid, y_grid, amp=1., sigma=sigma, center_x=0, center_y=0) kernel /= np.sum(kernel) kernel = util.array2image(kernel) return kernel
def __init__(self, psf_type='NONE', fwhm=None, truncation=5, pixel_size=None, kernel_point_source=None, psf_error_map=None, point_source_supersampling_factor=1, kernel_point_source_init=None): """ :param psf_type: string, type of PSF: options are 'NONE', 'PIXEL', 'GAUSSIAN' :param fwhm: float, full width at half maximum, only required for 'GAUSSIAN' model :param truncation: float, Gaussian truncation (in units of sigma), only required for 'GAUSSIAN' model :param pixel_size: width of pixel (required for Gaussian model, not required when using in combination with ImageModel modules) :param kernel_point_source: 2d numpy array, odd length, centered PSF of a point source (if not normalized, will be normalized) :param psf_error_map: uncertainty in the PSF model per pixel (size of data, not super-sampled). 2d numpy array. Size can be larger or smaller than the pixel-sized PSF model and if so, will be matched. This error will be added to the pixel error around the position of point sources as follows: sigma^2_i += 'psf_error_map'_j * (point_source_flux_i)**2 :param point_source_supersampling_factor: int, supersampling factor of kernel_point_source :param kernel_point_source_init: memory of an initial point source kernel that gets passed through the psf iteration """ self.psf_type = psf_type self._pixel_size = pixel_size self.kernel_point_source_init = kernel_point_source_init if self.psf_type == 'GAUSSIAN': if fwhm is None: raise ValueError('fwhm must be set for GAUSSIAN psf type!') self._fwhm = fwhm self._sigma_gaussian = util.fwhm2sigma(self._fwhm) self._truncation = truncation self._point_source_supersampling_factor = 0 elif self.psf_type == 'PIXEL': if kernel_point_source is None: raise ValueError('kernel_point_source needs to be specified for PIXEL PSF type!') if len(kernel_point_source) % 2 == 0: raise ValueError('kernel needs to have odd axis number, not ', np.shape(kernel_point_source)) if point_source_supersampling_factor > 1: self._kernel_point_source_supersampled = kernel_point_source self._point_source_supersampling_factor = point_source_supersampling_factor kernel_point_source = kernel_util.degrade_kernel(self._kernel_point_source_supersampled, self._point_source_supersampling_factor) self._kernel_point_source = kernel_point_source / np.sum(kernel_point_source) elif self.psf_type == 'NONE': self._kernel_point_source = np.zeros((3, 3)) self._kernel_point_source[1, 1] = 1 else: raise ValueError("psf_type %s not supported!" % self.psf_type) if psf_error_map is not None: n_kernel = len(self.kernel_point_source) self._psf_error_map = kernel_util.match_kernel_size(psf_error_map, n_kernel) if self.psf_type == 'PIXEL' and point_source_supersampling_factor > 1: if len(psf_error_map) == len(self._kernel_point_source_supersampled): Warning('psf_error_map has the same size as the super-sampled kernel. Make sure the units in the' 'psf_error_map are on the down-sampled pixel scale.') self.psf_error_map_bool = True else: self.psf_error_map_bool = False
def psf_configure_simple(psf_type="GAUSSIAN", fwhm=1, kernelsize=11, deltaPix=1, truncate=6, kernel=None): """ this routine generates keyword arguments to initialize a PSF() class in lenstronomy. Have a look at the PSF class documentation to see the full possibilities. :param psf_type: string, type of PSF model :param fwhm: Full width at half maximum of PSF (if GAUSSIAN psf) :param kernelsize: size in pixel of kernel (use odd numbers), only applicable for PIXEL kernels :param deltaPix: pixel size in angular units (only needed for GAUSSIAN kernel :param truncate: how many sigmas out is the truncation happening :param kernel: 2d numpy arra centered PSF (odd number per axis) :return: keyword arguments """ if psf_type == 'GAUSSIAN': sigma = util.fwhm2sigma(fwhm) sigma_axis = sigma gaussian = Gaussian() x_grid, y_grid = util.make_grid(kernelsize, deltaPix) kernel_large = gaussian.function(x_grid, y_grid, amp=1., sigma_x=sigma_axis, sigma_y=sigma_axis, center_x=0, center_y=0) kernel_large /= np.sum(kernel_large) kernel_large = util.array2image(kernel_large) kernel_pixel = kernel_util.pixel_kernel(kernel_large) kwargs_psf = { 'psf_type': psf_type, 'fwhm': fwhm, 'truncation': truncate * fwhm, 'kernel_point_source': kernel_large, 'kernel_pixel': kernel_pixel, 'pixel_size': deltaPix } elif psf_type == 'PIXEL': kernel_large = copy.deepcopy(kernel) kernel_large = kernel_util.cut_psf(kernel_large, psf_size=kernelsize) kwargs_psf = {'psf_type': "PIXEL", 'kernel_point_source': kernel_large} elif psf_type == 'NONE': kwargs_psf = {'psf_type': 'NONE'} else: raise ValueError("psf type %s not supported!" % psf_type) return kwargs_psf
def __init__(self, kwargs_psf): self.psf_type = kwargs_psf.get('psf_type', 'NONE') if self.psf_type == 'GAUSSIAN': self._fwhm = kwargs_psf['fwhm'] self._sigma_gaussian = util.fwhm2sigma(self._fwhm) self._truncation = kwargs_psf.get('truncation', 5 * self._fwhm) if 'pixel_size' in kwargs_psf: self._pixel_size = kwargs_psf['pixel_size'] elif self.psf_type == 'PIXEL': if 'kernel_point_source_subsampled' in kwargs_psf: self._kernel_point_source_subsampled = kwargs_psf[ 'kernel_point_source_subsampled'] n_high = len(self._kernel_point_source_subsampled) self._point_source_subsampling_factor = kwargs_psf[ 'point_source_subsampling_factor'] numPix = int(n_high / self._point_source_subsampling_factor) self._kernel_point_source = util.averaging( self._kernel_point_source_subsampled, numGrid=n_high, numPix=numPix) else: self._kernel_point_source = kwargs_psf['kernel_point_source'] if 'kernel_pixel_subsampled' in kwargs_psf: self._kernel_pixel_subsampled = kwargs_psf[ 'kernel_pixel_subsampled'] n_high = len(self._kernel_point_source_subsampled) self._pixel_subsampling_factor = kwargs_psf[ 'pixel_subsampling_factor'] numPix = int(n_high / self._pixel_subsampling_factor) self._kernel_pixel = util.averaging( self._kernel_pixel_subsampled, numGrid=n_high, numPix=numPix) else: if 'kernel_pixel' in kwargs_psf: self._kernel_pixel = kwargs_psf['kernel_pixel'] else: self._kernel_pixel = kernel_util.pixel_kernel( self._kernel_point_source, subgrid_res=1) elif self.psf_type == 'NONE': self._kernel_point_source = np.zeros((3, 3)) self._kernel_point_source[1, 1] = 1 else: raise ValueError("psf_type %s not supported!" % self.psf_type) if 'psf_error_map' in kwargs_psf: self._psf_error_map = kwargs_psf['psf_error_map'] if len(self._psf_error_map) != len(self._kernel_point_source): raise ValueError( 'psf_error_map must have same size as kernel_point_source!' )
def __init__(self, kwargs_psf): self.psf_type = kwargs_psf.get('psf_type', 'NONE') if self.psf_type == 'GAUSSIAN': self._fwhm = kwargs_psf['fwhm'] self._sigma_gaussian = util.fwhm2sigma(self._fwhm) self._truncation = kwargs_psf.get('truncation', 5 * self._fwhm) if 'pixel_size' in kwargs_psf: self._pixel_size = kwargs_psf['pixel_size'] elif self.psf_type == 'PIXEL': self._kernel_point_source = kwargs_psf['kernel_point_source'] if 'kernel_pixel' in kwargs_psf: self._kernel_pixel = kwargs_psf['kernel_pixel'] else: self._kernel_pixel = kernel_util.pixel_kernel(self._kernel_point_source, subgrid_res=7) elif self.psf_type == 'NONE': self._kernel_point_source = np.zeros((3, 3)) else: raise ValueError("psf_type %s not supported!" % self.psf_type) if 'psf_error_map' in kwargs_psf: self._psf_error_map = kwargs_psf['psf_error_map']
def setup(self): # data specifics sigma_bkg = 0.01 # background noise per pixel exp_time = 100 # exposure time (arbitrary units, flux per pixel is in units #photons/exp_time unit) numPix = 100 # cutout pixel size deltaPix = 0.05 # pixel size in arcsec (area per pixel = deltaPix**2) fwhm = 0.3 # full width half max of PSF # PSF specification kwargs_data = sim_util.data_configure_simple(numPix, deltaPix, exp_time, sigma_bkg) data_class = Data(kwargs_data) sigma = util.fwhm2sigma(fwhm) x_grid, y_grid = util.make_grid(numPix=31, deltapix=0.05) from lenstronomy.LightModel.Profiles.gaussian import Gaussian gaussian = Gaussian() kernel_point_source = gaussian.function(x_grid, y_grid, amp=1., sigma_x=sigma, sigma_y=sigma, center_x=0, center_y=0) kernel_point_source /= np.sum(kernel_point_source) kernel_point_source = util.array2image(kernel_point_source) self.kwargs_psf = {'psf_type': 'PIXEL', 'kernel_point_source': kernel_point_source} psf_class = PSF(kwargs_psf=self.kwargs_psf) # 'EXERNAL_SHEAR': external shear kwargs_shear = {'e1': 0.01, 'e2': 0.01} # gamma_ext: shear strength, psi_ext: shear angel (in radian) phi, q = 0.2, 0.8 e1, e2 = param_util.phi_q2_ellipticity(phi, q) kwargs_spemd = {'theta_E': 1., 'gamma': 1.8, 'center_x': 0, 'center_y': 0, 'e1': e1, 'e2': e2} lens_model_list = ['SPEP', 'SHEAR'] self.kwargs_lens = [kwargs_spemd, kwargs_shear] lens_model_class = LensModel(lens_model_list=lens_model_list) # list of light profiles (for lens and source) # 'SERSIC': spherical Sersic profile kwargs_sersic = {'amp': 1., 'R_sersic': 0.1, 'n_sersic': 2, 'center_x': 0, 'center_y': 0} # 'SERSIC_ELLIPSE': elliptical Sersic profile phi, q = 0.2, 0.9 e1, e2 = param_util.phi_q2_ellipticity(phi, q) kwargs_sersic_ellipse = {'amp': 1., 'R_sersic': .6, 'n_sersic': 7, 'center_x': 0, 'center_y': 0, 'e1': e1, 'e2': e2} lens_light_model_list = ['SERSIC'] self.kwargs_lens_light = [kwargs_sersic] lens_light_model_class = LightModel(light_model_list=lens_light_model_list) source_model_list = ['SERSIC_ELLIPSE'] self.kwargs_source = [kwargs_sersic_ellipse] source_model_class = LightModel(light_model_list=source_model_list) self.kwargs_ps = [{'ra_source': 0.0, 'dec_source': 0.0, 'source_amp': 10.}] # quasar point source position in the source plane and intrinsic brightness point_source_class = PointSource(point_source_type_list=['SOURCE_POSITION'], fixed_magnification_list=[True]) kwargs_numerics = {'subgrid_res': 3, 'psf_subgrid': True} imageModel = ImageModel(data_class, psf_class, lens_model_class, source_model_class, lens_light_model_class, point_source_class, kwargs_numerics=kwargs_numerics) image_sim = sim_util.simulate_simple(imageModel, self.kwargs_lens, self.kwargs_source, self.kwargs_lens_light, self.kwargs_ps) data_class.update_data(image_sim) self.imageModel = ImageModel(data_class, psf_class, lens_model_class, source_model_class, lens_light_model_class, point_source_class, kwargs_numerics=kwargs_numerics) self.psf_fitting = PsfFitting(self.imageModel)
def test_fwhm2sigma(): fwhm = 0.5 sigma = util.fwhm2sigma(fwhm) assert sigma == fwhm / (2 * np.sqrt(2 * np.log(2)))
def __init__(self, pixel_grid, psf, supersampling_factor=1, compute_mode='regular', supersampling_convolution=False, supersampling_kernel_size=5, flux_evaluate_indexes=None, supersampled_indexes=None, compute_indexes=None, point_source_supersampling_factor=1, convolution_kernel_size=None, convolution_type='fft_static', truncation=4): """ :param pixel_grid: PixelGrid() class instance :param psf: PSF() class instance :param compute_mode: options are: 'regular', 'adaptive' :param supersampling_factor: int, factor of higher resolution sub-pixel sampling of surface brightness :param supersampling_convolution: bool, if True, performs (part of) the convolution on the super-sampled grid/pixels :param supersampling_kernel_size: int (odd number), size (in regular pixel units) of the super-sampled convolution :param flux_evaluate_indexes: boolean 2d array of size of image (or None, then initiated as gird of True's). Pixels indicated with True will be used to perform the surface brightness computation (and possible lensing ray-shooting). Pixels marked as False will be assigned a flux value of zero (or ignored in the adaptive convolution) :param supersampled_indexes: 2d boolean array (only used in mode='adaptive') of pixels to be supersampled (in surface brightness and if supersampling_convolution=True also in convolution) :param compute_indexes: 2d boolean array (only used in mode='adaptive'), marks pixel that the resonse after convolution is computed (all others =0). This can be set to likelihood_mask in the Likelihood module for consistency. :param point_source_supersampling_factor: super-sampling resolution of the point source placing :param convolution_kernel_size: int, odd number, size of convolution kernel. If None, takes size of point_source_kernel :param convolution_type: string, 'fft', 'grid', 'fft_static' mode of 2d convolution """ if compute_mode not in ['regular', 'adaptive']: raise ValueError( 'compute_mode specified as %s not valid. Options are "adaptive", "regular"' ) # if no super sampling, turn the supersampling convolution off self._psf_type = psf.psf_type if not isinstance(supersampling_factor, int): raise TypeError( 'supersampling_factor needs to be an integer! Current type is %s' % type(supersampling_factor)) if supersampling_factor == 1: supersampling_convolution = False self._pixel_width = pixel_grid.pixel_width nx, ny = pixel_grid.num_pixel_axes transform_pix2angle = pixel_grid.transform_pix2angle ra_at_xy_0, dec_at_xy_0 = pixel_grid.radec_at_xy_0 if supersampled_indexes is None: supersampled_indexes = np.zeros((nx, ny), dtype=bool) if compute_mode == 'adaptive': # or (compute_mode == 'regular' and supersampling_convolution is False and supersampling_factor > 1): self._grid = AdaptiveGrid(nx, ny, transform_pix2angle, ra_at_xy_0, dec_at_xy_0, supersampled_indexes, supersampling_factor, flux_evaluate_indexes) else: self._grid = RegularGrid(nx, ny, transform_pix2angle, ra_at_xy_0, dec_at_xy_0, supersampling_factor, flux_evaluate_indexes) if self._psf_type == 'PIXEL': if compute_mode == 'adaptive' and supersampling_convolution is True: from lenstronomy.ImSim.Numerics.adaptive_numerics import AdaptiveConvolution kernel_super = psf.kernel_point_source_supersampled( supersampling_factor) kernel_super = self._supersampling_cut_kernel( kernel_super, convolution_kernel_size, supersampling_factor) self._conv = AdaptiveConvolution( kernel_super, supersampling_factor, conv_supersample_pixels=supersampled_indexes, supersampling_kernel_size=supersampling_kernel_size, compute_pixels=compute_indexes, nopython=True, cache=True, parallel=False) elif compute_mode == 'regular' and supersampling_convolution is True: kernel_super = psf.kernel_point_source_supersampled( supersampling_factor) if convolution_kernel_size is not None: kernel_super = psf.kernel_point_source_supersampled( supersampling_factor) kernel_super = self._supersampling_cut_kernel( kernel_super, convolution_kernel_size, supersampling_factor) self._conv = SubgridKernelConvolution( kernel_super, supersampling_factor, supersampling_kernel_size=supersampling_kernel_size, convolution_type=convolution_type) else: kernel = psf.kernel_point_source kernel = self._supersampling_cut_kernel( kernel, convolution_kernel_size, supersampling_factor=1) self._conv = PixelKernelConvolution( kernel, convolution_type=convolution_type) elif self._psf_type == 'GAUSSIAN': pixel_scale = pixel_grid.pixel_width fwhm = psf.fwhm # FWHM in units of angle sigma = util.fwhm2sigma(fwhm) sigma_list = [sigma] fraction_list = [1] self._conv = MultiGaussianConvolution(sigma_list, fraction_list, pixel_scale, supersampling_factor, supersampling_convolution, truncation=truncation) elif self._psf_type == 'NONE': self._conv = None else: raise ValueError( 'psf_type %s not valid! Chose either NONE, GAUSSIAN or PIXEL.' % self._psf_type) super(Numerics, self).__init__( pixel_grid=pixel_grid, supersampling_factor=point_source_supersampling_factor, psf=psf) if supersampling_convolution is True: self._high_res_return = True else: self._high_res_return = False
def magnification_finite_adaptive(self, x_image, y_image, source_x, source_y, kwargs_lens, source_fwhm_parsec, z_source, cosmo=None, grid_resolution=None, grid_radius_arcsec=None, axis_ratio=0.5, tol=0.001, step_size=0.05, use_largest_eigenvalue=True): """ This method computes image magnifications with a finite-size background source assuming a Gaussian source light profile. It can be much faster that magnification_finite for lens models with many deflectors and a relatively compact source. This is because most pixels in a rectangular window around a lensed image of a compact source will contain zero flux, and therefore don't contribute to the image brightness. Rather than ray tracing through a rectangular grid, this routine accelerates the computation of image magnifications with finite-size sources by ray tracing through an elliptical aperture oriented such that it resembles the surface brightness of the lensed image itself. The aperture size is initially quite small, and increases in size until the flux inside of it (and hence the magnification) converges. The orientation of the elliptical aperture is computed from the magnification tensor at the image coordinate. If for whatever reason you prefer a circular aperture to the elliptical approximation using the hessian eigenvectors, you can just set axis_ratio = 1. To use the eigenvalues of the hessian matrix to estimate the optimum axis ratio, set axis_ratio = 0. The default settings for the grid resolution and ray tracing window size work well for sources with fwhm between 0.5 - 100 pc. :param x_image: a list or array of x coordinates [units arcsec] :param y_image: a list or array of y coordinates [units arcsec] :param kwargs_lens: keyword arguments for the lens model :param source_fwhm_parsec: the size of the background source [units parsec] :param z_source: the source redshift :param cosmo: (optional) an instance of astropy.cosmology; if not specified, a default cosmology will be used :param grid_resolution: the grid resolution in units arcsec/pixel; if not specified, an appropriate value will be estimated from the source size :param grid_radius_arcsec: (optional) the size of the ray tracing region in arcsec; if not specified, an appropriate value will be estimated from the source size :param axis_ratio: the axis ratio of the ellipse used for ray tracing; if axis_ratio = 0, then the eigenvalues the hessian matrix will be used to estimate an appropriate axis ratio. Be warned: if the image is highly magnified it will tend to curve out of the resulting ellipse :param tol: tolerance for convergence in the magnification :param step_size: sets the increment for the successively larger ray tracing windows :param use_largest_eigenvalue: bool; if True, then the major axis of the ray tracing ellipse region will be aligned with the eigenvector corresponding to the largest eigenvalue of the hessian matrix :return: an array of image magnifications """ if cosmo is None: cosmo = self._lensModel.cosmo if grid_radius_arcsec is None: grid_radius_arcsec = auto_raytracing_grid_size(source_fwhm_parsec) if grid_resolution is None: grid_resolution = auto_raytracing_grid_resolution( source_fwhm_parsec) pc_per_arcsec = 1000 / cosmo.arcsec_per_kpc_proper(z_source).value source_fwhm_arcsec = source_fwhm_parsec / pc_per_arcsec source_sigma_arcsec = fwhm2sigma(source_fwhm_arcsec) kwargs_source = [{ 'amp': 1., 'center_x': source_x, 'center_y': source_y, 'sigma': source_sigma_arcsec }] source_model = LightModel(['GAUSSIAN']) npix = int(2 * grid_radius_arcsec / grid_resolution) _grid_x = np.linspace(-grid_radius_arcsec, grid_radius_arcsec, npix) _grid_y = np.linspace(-grid_radius_arcsec, grid_radius_arcsec, npix) magnifications = [] minimum_magnification = 1e-4 grid_x_0, grid_y_0 = np.meshgrid(_grid_x, _grid_y) grid_x_0, grid_y_0 = grid_x_0.ravel(), grid_y_0.ravel() for xi, yi in zip(x_image, y_image): w1, w2, v11, v12, v21, v22 = self.hessian_eigenvectors( xi, yi, kwargs_lens) _v = [np.array([v11, v12]), np.array([v21, v22])] _w = [abs(w1), abs(w2)] if use_largest_eigenvalue: idx = int(np.argmax(_w)) else: idx = int(np.argmin(_w)) v = _v[idx] rotation_angle = np.arctan(v[1] / v[0]) - np.pi / 2 grid_x, grid_y = util.rotate(grid_x_0, grid_y_0, rotation_angle) if axis_ratio == 0: sort = np.argsort(_w) q = _w[sort[0]] / _w[sort[1]] grid_r = np.hypot(grid_x, grid_y / q).ravel() else: grid_r = np.hypot(grid_x, grid_y / axis_ratio).ravel() flux_array = np.zeros_like(grid_x_0) step = step_size * grid_radius_arcsec r_min = 0 r_max = step magnification_current = 0. while True: flux_array = self._magnification_adaptive_iteration( flux_array, xi, yi, grid_x_0, grid_y_0, grid_r, r_min, r_max, self._lensModel, kwargs_lens, source_model, kwargs_source) new_magnification = np.sum(flux_array) * grid_resolution**2 diff = abs(new_magnification - magnification_current) / new_magnification # the sqrt(2) will allow this algorithm to fill up the entire square window if r_max > np.sqrt(2) * grid_radius_arcsec: break elif diff < tol and new_magnification > minimum_magnification: break else: r_min += step r_max += step magnification_current = new_magnification magnifications.append(new_magnification) return np.array(magnifications)
def setup_mag_finite(cosmo, lens_model, grid_radius_arcsec, grid_resolution, source_fwhm_parsec, source_light_model, z_source, source_x, source_y, dx, dy, amp_scale, size_scale): """ Sets up the ray tracing grid and source light model for magnification_finite_adaptive and plot_quasar_images routines :param cosmo: (optional) an instance of astropy.cosmology; if not specified, a default cosmology will be used :param lens_model: an instance of LensModel :param grid_radius_arcsec: (optional) the size of the ray tracing region in arcsec; if not specified, an appropriate value will be estimated from the source size :param grid_resolution: the grid resolution in units arcsec/pixel; if not specified, an appropriate value will be estimated from the source size :param source_fwhm_parsec: the size of the background source [units parsec] :param source_light_model: the model for background source light; currently implemented are 'SINGLE_GAUSSIAN' and 'DOUBLE_GAUSSIAN'. :param z_source: source redshift :param source_x: source x position [arcsec] :param source_y: source y position [arcsec] :param dx: used with source model 'DOUBLE_GAUSSIAN', the offset of the second source light profile from the first [arcsec] :param dy: used with source model 'DOUBLE_GAUSSIAN', the offset of the second source light profile from the first [arcsec] :param amp_scale: used with source model 'DOUBLE_GAUSSIAN', the peak brightness of the second source light profile relative to the first :param size_scale: used with source model 'DOUBLE_GAUSSIAN', the size of the second source light profile relative to the first :return: x coordinate grid, y coordinate grid, source light model, and keywords for the source light model """ if cosmo is None: cosmo = lens_model.cosmo if grid_radius_arcsec is None: grid_radius_arcsec = auto_raytracing_grid_size(source_fwhm_parsec) if grid_resolution is None: grid_resolution = auto_raytracing_grid_resolution(source_fwhm_parsec) pc_per_arcsec = 1000 / cosmo.arcsec_per_kpc_proper(z_source).value source_fwhm_arcsec = source_fwhm_parsec / pc_per_arcsec source_sigma_arcsec = fwhm2sigma(source_fwhm_arcsec) if source_light_model == 'SINGLE_GAUSSIAN': kwargs_source = [{'amp': 1., 'center_x': source_x, 'center_y': source_y, 'sigma': source_sigma_arcsec}] source_model = LightModel(['GAUSSIAN']) elif source_light_model == 'DOUBLE_GAUSSIAN': amp_1 = 1. kwargs_source_1 = [{'amp': amp_1, 'center_x': source_x, 'center_y': source_y, 'sigma': source_sigma_arcsec}] # c = amp / (2 * np.pi * sigma**2) amp_2 = amp_1 * amp_scale * size_scale ** 2 kwargs_source_2 = [{'amp': amp_2, 'center_x': source_x + dx, 'center_y': source_y + dy, 'sigma': source_sigma_arcsec * size_scale}] kwargs_source = kwargs_source_1 + kwargs_source_2 source_model = LightModel(['GAUSSIAN'] * 2) else: raise Exception('source light model must be specified, currently implemented models are SINGLE_GAUSSIAN ' 'and DOUBLE_GAUSSIAN') npix = int(2 * grid_radius_arcsec / grid_resolution) _grid_x = np.linspace(-grid_radius_arcsec, grid_radius_arcsec, npix) _grid_y = np.linspace(-grid_radius_arcsec, grid_radius_arcsec, npix) grid_x_0, grid_y_0 = np.meshgrid(_grid_x, _grid_y) return grid_x_0, grid_y_0, source_model, kwargs_source, grid_resolution, grid_radius_arcsec