From a9f3cfa93639c342376cb48225f52aa1d04713d6 Mon Sep 17 00:00:00 2001 From: narayan Date: Tue, 8 Oct 2024 12:34:23 -0400 Subject: [PATCH 01/87] added false_positive and false_positive_pop. --- slsim/false_positive.py | 776 ++++++++++++++++++++++++++++++++++++ slsim/false_positive_pop.py | 221 ++++++++++ 2 files changed, 997 insertions(+) create mode 100644 slsim/false_positive.py create mode 100644 slsim/false_positive_pop.py diff --git a/slsim/false_positive.py b/slsim/false_positive.py new file mode 100644 index 000000000..0e42ac902 --- /dev/null +++ b/slsim/false_positive.py @@ -0,0 +1,776 @@ +import warnings + +import numpy as np +from lenstronomy.Analysis.lens_profile import LensProfileAnalysis +from lenstronomy.Cosmo.lens_cosmo import LensCosmo +from lenstronomy.LensModel.lens_model import LensModel +from lenstronomy.LensModel.Solver.lens_equation_solver import LensEquationSolver +from lenstronomy.LensModel.Solver.lens_equation_solver import ( + analytical_lens_model_support, +) +from slsim.ParamDistributions.los_config import LOSConfig +from slsim.Util.param_util import ellipticity_slsim_to_lenstronomy +from lenstronomy.LightModel.light_model import LightModel +from lenstronomy.Util import constants +from lenstronomy.Util import data_util +from lenstronomy.Util import util + +from slsim.lensed_system_base import LensedSystemBase + + +class Lens(LensedSystemBase): + """Class to manage individual lenses.""" + + def __init__( + self, + source_dict, + deflector_dict, + cosmo, + deflector_type="EPL", + source_type="extended", + lens_equation_solver="lenstronomy_analytical", + variability_model=None, + kwargs_variability=None, + sn_type=None, + sn_absolute_mag_band=None, + sn_absolute_zpsys=None, + test_area=4 * np.pi, + mixgauss_means=None, + mixgauss_stds=None, + mixgauss_weights=None, + magnification_limit=0.01, + light_profile="single_sersic", + lightcurve_time=None, + los_config=None, + sn_modeldir=None, + los_dict=None, + agn_driving_variability_model=None, + agn_driving_kwargs_variability=None, + ): + """ + + :param source_dict: source properties + :type source_dict: dict or astropy table + :param deflector_dict: deflector properties + :type deflector_dict: dict + :param cosmo: astropy.cosmology instance + :param deflector_type: type of deflector, i.e. "EPL", "NFW_HERNQUIST", "NFW_CLUSTER" + :type deflector_type: str + :param source_type: type of the source 'extended' or 'point_source' or + 'point_plus_extended' supported + :type source_type: str + :param lens_equation_solver: type of lens equation solver; currently supporting + "lenstronomy_analytical" and "lenstronomy_general" + :type lens_equation_solver: str + :param variability_model: keyword for variability model to be used. This is an + input for the Variability class. + :type variability_model: str + :param kwargs_variability: keyword arguments for the variability of a source. + This is associated with an input for Variability class. + :type kwargs_variability: list of str + :param sn_type: Supernova type (Ia, Ib, Ic, IIP, etc.) + :type sn_type: str + :param sn_absolute_mag_band: Band used to normalize to absolute magnitude + :type sn_absolute_mag_band: str or `~sncosmo.Bandpass` + :param sn_absolute_zpsys: Optional, AB or Vega (AB default) + :type sn_absolute_zpsys: str + :param test_area: area of disk around one lensing galaxies to be investigated + on (in arc-seconds^2) + :param magnification_limit: absolute lensing magnification lower limit to + register a point source (ignore highly de-magnified images) + :type magnification_limit: float >= 0 + :param light_profile: keyword for number of sersic profile to use in source + light model + :type light_profile: str . Either "single_sersic" or "double_sersic" . + :param lightcurve_time: observation time array for lightcurve in unit of days. + :type lightcurve_time: array + :param sn_modeldir: sn_modeldir is the path to the directory containing files + needed to initialize the sncosmo.model class. For example, + sn_modeldir = 'C:/Users/username/Documents/SALT3.NIR_WAVEEXT'. These data can + be downloaded from https://github.com/LSST-strong-lensing/data_public . + For more detail, please look at the documentation of RandomizedSupernovae + class. + :type sn_modeldir: str + :param los_dict: line of sight dictionary (optional, takes these values instead of drawing from distribution) + Takes "gamma" = [gamma1, gamma2] and "kappa" = kappa as entries + :type los_dict: dict + :param agn_driving_variability_model: Variability model with light_curve output + which drives the variability across all bands of the agn. + :type agn_driving_variability_model: str (e.g. "light_curve", "sinusoidal", "bending_power_law") + :param agn_driving_kwargs_variability: Dictionary containing agn variability + parameters for the driving variability class. eg: variable_agn_kwarg_dict = + {"length_of_light_curve": 1000, "time_resolution": 1, + "log_breakpoint_frequency": 1 / 20, "low_frequency_slope": 1, + "high_frequency_slope": 3, "normal_magnitude_variance": 0.1}. For the detailed + explanation of these parameters, see generate_signal() function in + astro_util.py. + :type agn_driving_kwargs_variability: dict + """ + super().__init__( + source_dict=source_dict, + deflector_dict=deflector_dict, + cosmo=cosmo, + deflector_type=deflector_type, + test_area=test_area, + variability_model=variability_model, + kwargs_variability=kwargs_variability, + lightcurve_time=lightcurve_time, + sn_type=sn_type, + sn_absolute_mag_band=sn_absolute_mag_band, + sn_absolute_zpsys=sn_absolute_zpsys, + sn_modeldir=sn_modeldir, + agn_driving_variability_model=agn_driving_variability_model, + agn_driving_kwargs_variability=agn_driving_kwargs_variability, + ) + + self.cosmo = cosmo + self._source_type = source_type + self._lens_equation_solver = lens_equation_solver + self._magnification_limit = magnification_limit + self.kwargs_variab = kwargs_variability + self.light_profile = light_profile + + if self._source_type == "extended" and self.kwargs_variab is not None: + warning_msg = ( + "Extended source can not have variability. Therefore," + "variability information provided by you will not be used." + ) + warnings.warn(warning_msg, category=UserWarning, stacklevel=2) + + self._lens_cosmo = LensCosmo( + z_lens=float(self.deflector.redshift), + z_source=float(self.source.redshift), + cosmo=self.cosmo, + ) + + self._los_linear_distortions_cache = None + self.los_config = los_config + if self.los_config is None: + if los_dict is None: + los_dict = {} + self.los_config = LOSConfig(**los_dict) + + @property + def image_number(self): + """Number of images in the lensing configuration. + + :return: number of images + """ + return len(self.point_source_image_positions()[0]) + + @property + def deflector_position(self): + """Center of the deflector position. + + :return: [x_pox, y_pos] in arc seconds + """ + return self.deflector.deflector_center + + def extended_source_image_positions(self): + """Returns extended source image positions by solving the lens equation. + + :return: x-pos, y-pos + """ + if not hasattr(self, "_image_positions"): + lens_model_list, kwargs_lens = self.deflector_mass_model_lenstronomy() + lens_model_class = LensModel(lens_model_list=lens_model_list) + lens_eq_solver = LensEquationSolver(lens_model_class) + source_pos_x, source_pos_y = self.source.extended_source_position( + center_lens=self.deflector_position, draw_area=self.test_area + ) + if ( + self._lens_equation_solver == "lenstronomy_analytical" + and analytical_lens_model_support(lens_model_list) is True + ): + solver = "analytical" + else: + solver = "lenstronomy" + self._image_positions = lens_eq_solver.image_position_from_source( + source_pos_x, + source_pos_y, + kwargs_lens, + solver=solver, + search_window=self.einstein_radius * 6, + min_distance=self.einstein_radius * 6 / 200, + magnification_limit=self._magnification_limit, + ) + return self._image_positions + + def point_source_image_positions(self): + """Returns point source image positions by solving the lens equation. In the + absence of a point source, this function returns the solution for the center of + the extended source. + + :return: x-pos, y-pos + """ + if not hasattr(self, "_point_image_positions"): + lens_model_list, kwargs_lens = self.deflector_mass_model_lenstronomy() + lens_model_class = LensModel(lens_model_list=lens_model_list) + lens_eq_solver = LensEquationSolver(lens_model_class) + point_source_pos_x, point_source_pos_y = self.source.point_source_position( + center_lens=self.deflector_position, draw_area=self.test_area + ) + # uses analytical lens equation solver in case it is supported by lenstronomy for speed-up + if ( + self._lens_equation_solver == "lenstronomy_analytical" + and analytical_lens_model_support(lens_model_list) is True + ): + solver = "analytical" + else: + solver = "lenstronomy" + self._point_image_positions = lens_eq_solver.image_position_from_source( + point_source_pos_x, + point_source_pos_y, + kwargs_lens, + solver=solver, + search_window=self.einstein_radius * 6, + min_distance=self.einstein_radius * 6 / 200, + magnification_limit=self._magnification_limit, + ) + return self._point_image_positions + + def validity_test( + self, + min_image_separation=0, + max_image_separation=10, + mag_arc_limit=None, + ): + """Check whether lensing configuration matches selection and plausibility + criteria. + + :param min_image_separation: minimum image separation + :param max_image_separation: maximum image separation + :param mag_arc_limit: dictionary with key of bands and values of magnitude + limits of integrated lensed arc + :type mag_arc_limit: dict with key of bands and values of magnitude limits + :return: boolean + """ + # Criteria 1:The redshift of the lens (z_lens) must be less than the + # redshift of the source (z_source). + z_lens = self.deflector.redshift + z_source = self.source.redshift + if z_lens >= z_source: + return False + + # Criteria 2: The angular Einstein radius of the lensing configuration (theta_E) + # times 2 must be greater than or equal to the minimum image separation + # (min_image_separation) and less than or equal to the maximum image + # separation (max_image_separation). + if not min_image_separation <= 2 * self.einstein_radius <= max_image_separation: + return False + + # Criteria 3: The distance between the lens center and the source position + # must be less than or equal to the angular Einstein radius + # of the lensing configuration (times sqrt(2)). + center_lens, center_source = ( + self.deflector_position, + self.source.point_source_position( + center_lens=self.deflector_position, draw_area=self.test_area + ), + ) + if np.sum((center_lens - center_source) ** 2) > self.einstein_radius**2 * 2: + return False + + # Criteria 4: The lensing configuration must produce at least two SL images. + image_positions = self.point_source_image_positions() + if len(image_positions[0]) < 2: + return False + + # Criteria 5: The maximum separation between any two image positions must be + # greater than or equal to the minimum image separation and less than or + # equal to the maximum image separation. + image_separation = image_separation_from_positions(image_positions) + if not min_image_separation <= image_separation <= max_image_separation: + return False + + # Criteria 6: (optional) + # compute the magnified brightness of the lensed extended arc for different + # bands at least in one band, the magnitude has to be brighter than the limit + if mag_arc_limit is not None and self._source_type in [ + "extended", + "point_plus_extended", + ]: + # makes sure magnification of extended source is only used when there is + # an extended source + bool_mag_limit = False + host_mag = self.extended_source_magnification() + for band, mag_limit_band in mag_arc_limit.items(): + mag_source = self.extended_source_magnitude(band) + mag_arc = mag_source - 2.5 * np.log10( + host_mag + ) # lensing magnification results in a shift in magnitude + if mag_arc < mag_limit_band: + bool_mag_limit = True + break + if bool_mag_limit is False: + return False + # TODO make similar criteria for point source magnitudes + return True + # TODO: test for signal-to-noise ratio in surface brightness + + @property + def deflector_redshift(self): + """ + + :return: lens redshift + """ + return self.deflector.redshift + + @property + def source_redshift(self): + """ + + :return: source redshift + """ + return self.source.redshift + + @property + def external_convergence(self): + """ + + :return: external convergence + """ + _, _, kappa_ext = self.los_linear_distortions + return kappa_ext + + @property + def external_shear(self): + """ + + :return: the absolute external shear + """ + gamma1, gamma2, _ = self.los_linear_distortions + return (gamma1**2 + gamma2**2) ** 0.5 + + @property + def einstein_radius_deflector(self): + """Einstein radius, from SIS approximation (coming from velocity dispersion) + without line-of-sight correction. + + :return: + """ + if not hasattr(self, "_theta_E"): + if self.deflector.redshift >= self.source.redshift: + self._theta_E = 0 + elif self.deflector.deflector_type in ["EPL"]: + self._theta_E = self._lens_cosmo.sis_sigma_v2theta_E( + float(self.deflector.velocity_dispersion(cosmo=self.cosmo)) + ) + else: + # numerical solution for the Einstein radius + lens_model_list, kwargs_lens = self.deflector_mass_model_lenstronomy() + lens_model = LensModel(lens_model_list=lens_model_list) + lens_analysis = LensProfileAnalysis(lens_model=lens_model) + self._theta_E = lens_analysis.effective_einstein_radius( + kwargs_lens, r_min=1e-3, r_max=5e1, num_points=50 + ) + return self._theta_E + + @property + def einstein_radius(self): + """Einstein radius, from SIS approximation (coming from velocity dispersion) + + external convergence effect. + + :return: Einstein radius [arc seconds] + """ + theta_E = self.einstein_radius_deflector + _, _, kappa_ext = self.los_linear_distortions + return theta_E / (1 - kappa_ext) + + def deflector_ellipticity(self): + """ + + :return: e1_light, e2_light, e1_mass, e2_mass + """ + e1_light, e2_light = self.deflector.light_ellipticity + e1_mass, e2_mass = self.deflector.mass_ellipticity + return e1_light, e2_light, e1_mass, e2_mass + + def deflector_stellar_mass(self): + """ + + :return: stellar mass of deflector + """ + return self.deflector.stellar_mass + + def deflector_velocity_dispersion(self): + """ + + :return: velocity dispersion [km/s] + """ + return self.deflector.velocity_dispersion(cosmo=self.cosmo) + + @property + def los_linear_distortions(self): + if self._los_linear_distortions_cache is None: + self._los_linear_distortions_cache = ( + self._calculate_los_linear_distortions() + ) + return self._los_linear_distortions_cache + + def _calculate_los_linear_distortions(self): + """Line-of-sight distortions in shear and convergence. + + :return: kappa, gamma1, gamma2 + """ + return self.los_config.calculate_los_linear_distortions( + source_redshift=self.source_redshift, + deflector_redshift=self.deflector_redshift, + ) + + def deflector_magnitude(self, band): + """Apparent magnitude of the deflector for a given band. + + :param band: imaging band + :type band: string + :return: magnitude of deflector in given band + """ + return self.deflector.magnitude(band=band) + + def point_source_arrival_times(self): + """Arrival time of images relative to a straight line without lensing. Negative + values correspond to images arriving earlier, and positive signs correspond to + images arriving later. + + :return: arrival times for each image [days] + :rtype: numpy array + """ + lens_model_list, kwargs_lens = self.deflector_mass_model_lenstronomy() + lens_model = LensModel( + lens_model_list=lens_model_list, + cosmo=self.cosmo, + z_lens=self.deflector_redshift, + z_source=self.source_redshift, + ) + x_image, y_image = self.point_source_image_positions() + arrival_times = lens_model.arrival_time( + x_image, y_image, kwargs_lens=kwargs_lens + ) + return arrival_times + + def image_observer_times(self, t_obs): + """Calculates time of the source at the different images, not correcting for + redshifts, but for time delays. The time is relative to the first arriving + image. + + :param t_obs: time of observation [days]. It could be a single observation time + or an array of observation time. + :return: time of the source when seen in the different images (without redshift + correction) + :rtype: numpy array. Each element of the array corresponds to different image + observation times. + """ + arrival_times = self.point_source_arrival_times() + if type(t_obs) is np.ndarray and len(t_obs) > 1: + observer_times = ( + t_obs[:, np.newaxis] - arrival_times + np.min(arrival_times) + ).T + else: + observer_times = (t_obs - arrival_times + np.min(arrival_times))[ + :, np.newaxis + ] + + return observer_times + + def point_source_magnitude(self, band, lensed=False, time=None): + """Point source magnitude, either unlensed (single value) or lensed (array) with + macro-model magnifications. + + # TODO: time-variability with micro-lensing + + :param band: imaging band + :type band: string + :param lensed: if True, returns the lensed magnified magnitude + :type lensed: bool + :param time: time is a image observation time in units of days. If None, + provides magnitude without variability. + :return: point source magnitude + """ + # TODO: might have to change conventions between extended and point source + if lensed: + magnif = self.point_source_magnification() + magnif_log = 2.5 * np.log10(abs(magnif)) + if time is not None: + time = time + image_observed_times = self.image_observer_times(time) + variable_magnitude = self.source.point_source_magnitude( + band, + image_observation_times=image_observed_times, + ) + lensed_variable_magnitude = ( + variable_magnitude - magnif_log[:, np.newaxis] + ) + return lensed_variable_magnitude + else: + source_mag_unlensed = self.source.point_source_magnitude(band) + magnified_mag_list = [] + for i in range(len(magnif_log)): + magnified_mag_list.append(source_mag_unlensed - magnif_log[i]) + return np.array(magnified_mag_list) + return self.source.point_source_magnitude(band) + + def extended_source_magnitude(self, band, lensed=False): + """Unlensed apparent magnitude of the extended source for a given band (assumes + that size is the same for different bands) + + :param band: imaging band + :type band: string + :param lensed: if True, returns the lensed magnified magnitude + :type lensed: bool + :return: magnitude of source in given band + """ + # band_string = str("mag_" + band) + # TODO: might have to change conventions between extended and point source + source_mag = self.source.extended_source_magnitude(band) + if lensed: + mag = self.extended_source_magnification() + return source_mag - 2.5 * np.log10(mag) + return source_mag + + def point_source_magnification(self): + """Macro-model magnification of point sources. + + :return: signed magnification of point sources in same order as image positions + """ + if not hasattr(self, "_ps_magnification"): + lens_model_list, kwargs_lens = self.deflector_mass_model_lenstronomy() + lensModel = LensModel(lens_model_list=lens_model_list) + img_x, img_y = self.point_source_image_positions() + self._ps_magnification = lensModel.magnification(img_x, img_y, kwargs_lens) + return self._ps_magnification + + def extended_source_magnification(self): + """Compute the extended lensed surface brightness and calculates the integrated + flux-weighted magnification factor of the extended host galaxy. + + :return: integrated magnification factor of host magnitude + """ + if not hasattr(self, "_extended_source_magnification"): + kwargs_model, kwargs_params = self.lenstronomy_kwargs(band=None) + lightModel = LightModel( + light_model_list=kwargs_model.get("source_light_model_list", []) + ) + lensModel = LensModel( + lens_model_list=kwargs_model.get("lens_model_list", []) + ) + theta_E = self.einstein_radius + center_source = self.source.extended_source_position( + center_lens=self.deflector_position, draw_area=self.test_area + ) + + kwargs_source_mag = kwargs_params["kwargs_source"] + kwargs_source_amp = data_util.magnitude2amplitude( + lightModel, kwargs_source_mag, magnitude_zero_point=0 + ) + + num_pix = 200 + delta_pix = theta_E * 4 / num_pix + x, y = util.make_grid(numPix=num_pix, deltapix=delta_pix) + x += center_source[0] + y += center_source[1] + beta_x, beta_y = lensModel.ray_shooting(x, y, kwargs_params["kwargs_lens"]) + flux_lensed = np.sum( + lightModel.surface_brightness(beta_x, beta_y, kwargs_source_amp) + ) + flux_no_lens = np.sum( + lightModel.surface_brightness(x, y, kwargs_source_amp) + ) + if flux_no_lens > 0: + self._extended_source_magnification = flux_lensed / flux_no_lens + else: + self._extended_source_magnification = 0 + return self._extended_source_magnification + + def lenstronomy_kwargs(self, band=None): + """Generates lenstronomy dictionary conventions for the class object. + + :param band: imaging band, if =None, will result in un-normalized amplitudes + :type band: string or None + :return: lenstronomy model and parameter conventions + """ + lens_mass_model_list, kwargs_lens = self.deflector_mass_model_lenstronomy() + ( + lens_light_model_list, + kwargs_lens_light, + ) = self.deflector.light_model_lenstronomy(band=band) + + sources, sources_kwargs = self.source_light_model_lenstronomy(band=band) + combined_lens_light_model_list = lens_light_model_list + sources[ + "source_light_model_list"] + combined_kwargs_lens_light = kwargs_lens_light + sources_kwargs["kwargs_source"] + + kwargs_model = { + "lens_light_model_list": combined_lens_light_model_list, + "lens_model_list": lens_mass_model_list, + } + + kwargs_source = None + kwargs_ps = sources_kwargs["kwargs_ps"] + + kwargs_params = { + "kwargs_lens": kwargs_lens, + "kwargs_source": kwargs_source, + "kwargs_lens_light": combined_kwargs_lens_light, + "kwargs_ps": kwargs_ps, + } + + return kwargs_model, kwargs_params + + def deflector_mass_model_lenstronomy(self): + """Returns lens model instance and parameters in lenstronomy conventions. + + :return: lens_model_list, kwargs_lens + """ + if self.deflector.deflector_type in ["EPL", "NFW_HERNQUIST", "NFW_CLUSTER"]: + lens_mass_model_list, kwargs_lens = self.deflector.mass_model_lenstronomy( + lens_cosmo=self._lens_cosmo + ) + else: + raise ValueError( + "Deflector model %s not supported for lenstronomy model" + % self.deflector.deflector_type + ) + # adding line-of-sight structure + gamma1, gamma2, kappa_ext = self.los_linear_distortions + gamma1_lenstronomy, gamma2_lenstronomy = ellipticity_slsim_to_lenstronomy( + e1_slsim=gamma1, e2_slsim=gamma2 + ) + kwargs_lens.append( + { + "gamma1": gamma1_lenstronomy, + "gamma2": gamma2_lenstronomy, + "ra_0": 0, + "dec_0": 0, + } + ) + kwargs_lens.append({"kappa": kappa_ext, "ra_0": 0, "dec_0": 0}) + lens_mass_model_list.append("SHEAR") + lens_mass_model_list.append("CONVERGENCE") + + return lens_mass_model_list, kwargs_lens + + def deflector_light_model_lenstronomy(self, band): + """Returns lens model instance and parameters in lenstronomy conventions. + + :param band: imaging band + :type band: str + :return: lens_light_model_list, kwargs_lens_light + """ + return self.deflector.light_model_lenstronomy(band=band) + + def source_light_model_lenstronomy(self, band=None): + """Returns source light model instance and parameters in lenstronomy + conventions. + + :return: source_light_model_list, kwargs_source_light + """ + source_models = {} + all_source_kwarg_dict = {} + if ( + self._source_type == "extended" + or self._source_type == "point_plus_extended" + ): + if self.light_profile == "single_sersic": + source_models["source_light_model_list"] = ["SERSIC_ELLIPSE"] + else: + source_models["source_light_model_list"] = [ + "SERSIC_ELLIPSE", + "SERSIC_ELLIPSE", + ] + kwargs_source = self.source.kwargs_extended_source_light( + draw_area=self.test_area, + center_lens=self.deflector_position, + band=band, + light_profile_str=self.light_profile, + ) + else: + # source_models['source_light_model_list'] = None + kwargs_source = None + + if ( + self._source_type == "point_source" + or self._source_type == "point_plus_extended" + ): + source_models["point_source_model_list"] = ["LENSED_POSITION"] + img_x, img_y = self.point_source_image_positions() + if band is None: + image_magnitudes = np.abs(self.point_source_magnification()) + else: + image_magnitudes = self.point_source_magnitude(band=band, lensed=False) + kwargs_ps = [ + {"ra_image": img_x, "dec_image": img_y, "magnitude": image_magnitudes} + ] + else: + # source_models['point_source_model'] = None + kwargs_ps = None + all_source_kwarg_dict["kwargs_source"] = kwargs_source + all_source_kwarg_dict["kwargs_ps"] = kwargs_ps + return source_models, all_source_kwarg_dict + + def kappa_star(self, ra, dec): + """Computes the stellar surface density at location (ra, dec) in units of + lensing convergence. + + :param ra: position in the image plane + :param dec: position in the image plane + :return: kappa_star + """ + stellar_mass = self.deflector_stellar_mass() + kwargs_model, kwargs_params = self.lenstronomy_kwargs(band=None) + lightModel = LightModel( + light_model_list=kwargs_model.get("lens_light_model_list", []) + ) + kwargs_lens_light_mag = kwargs_params["kwargs_lens_light"] + kwargs_lens_light_amp = data_util.magnitude2amplitude( + lightModel, kwargs_lens_light_mag, magnitude_zero_point=0 + ) + + total_flux = lightModel.total_flux(kwargs_lens_light_amp) # integrated flux + flux_local = lightModel.surface_brightness( + ra, dec, kwargs_lens_light_amp + ) # surface brightness per arcsecond square + kappa_star = ( + flux_local / total_flux * stellar_mass / self._lens_cosmo.sigma_crit_angle + ) + return kappa_star + + +def image_separation_from_positions(image_positions): + """Calculate image separation in arc-seconds; if there are only two images, the + separation between them is returned; if there are more than 2 images, the maximum + separation is returned. + + :param image_positions: list of image positions in arc-seconds + :return: image separation in arc-seconds + """ + if len(image_positions[0]) == 2: + image_separation = np.sqrt( + (image_positions[0][0] - image_positions[0][1]) ** 2 + + (image_positions[1][0] - image_positions[1][1]) ** 2 + ) + else: + coords = np.stack((image_positions[0], image_positions[1]), axis=-1) + separations = np.sqrt( + np.sum((coords[:, np.newaxis] - coords[np.newaxis, :]) ** 2, axis=-1) + ) + image_separation = np.max(separations) + return image_separation + + +def theta_e_when_source_infinity(deflector_dict=None, v_sigma=None): + """Calculate Einstein radius in arc-seconds for a source at infinity. + + :param deflector_dict: deflector properties + :param v_sigma: velocity dispersion in km/s + :return: Einstein radius in arc-seconds + """ + if v_sigma is None: + if deflector_dict is None: + raise ValueError("Either deflector_dict or v_sigma must be provided") + else: + v_sigma = deflector_dict["vel_disp"] + + theta_E_infinity = ( + 4 * np.pi * (v_sigma * 1000.0 / constants.c) ** 2 / constants.arcsec + ) + return theta_E_infinity diff --git a/slsim/false_positive_pop.py b/slsim/false_positive_pop.py new file mode 100644 index 000000000..9c220c350 --- /dev/null +++ b/slsim/false_positive_pop.py @@ -0,0 +1,221 @@ +import sncosmo +import numpy as np + +from slsim.false_positive import Lens +from typing import Optional, Union +from astropy.cosmology import Cosmology +from slsim.lens import theta_e_when_source_infinity +from slsim.Sources.source_pop_base import SourcePopBase +from slsim.ParamDistributions.los_config import LOSConfig +from slsim.Deflectors.deflectors_base import DeflectorsBase +from slsim.lensed_population_base import LensedPopulationBase + + +class LensPop(LensedPopulationBase): + """Class to perform samples of lens population.""" + + def __init__( + self, + deflector_population: DeflectorsBase, + source_population: SourcePopBase, + cosmo: Optional[Cosmology] = None, + sky_area: Optional[float] = None, + lightcurve_time: Optional[np.ndarray] = None, + sn_type: Optional[str] = None, + sn_absolute_mag_band: Optional[Union[str, sncosmo.Bandpass]] = None, + sn_absolute_zpsys: Optional[str] = None, + los_config: Optional[LOSConfig] = None, + sn_modeldir: Optional[str] = None, + ): + """ + Args: + deflector_population (DeflectorsBase): Deflector population as an instance of a DeflectorsBase subclass. + source_population (SourcePopBase): Source population as an instance of a SourcePopBase subclass + cosmo (Optional[Cosmology], optional): AstroPy Cosmology instance. If None, defaults to flat LCDM with h0=0.7 and Om0=0.3. + Defaults to None. + lightcurve_time (Optional[np.ndarray], optional): Lightcurve observation time array in units of days. Defaults to None. + sn_type (Optional[str], optional): Supernova type (Ia, Ib, Ic, IIP, etc.). Defaults to None. + sn_absolute_mag_band (Optional[Union[str,sncosmo.Bandpass]], optional): Band used to normalize to absolute magnitude. + Defaults to None. + sn_absolute_zpsys (Optional[str], optional): Zero point system, either AB or Vega, with None defaulting to AB. + Defaults to None. + los_config (Optional[LOSConfig], optional): Configuration for line of sight distribution. Defaults to None. + sn_modeldir (Optional[str], optional): sn_modeldir is the path to the directory containing files needed to initialize + the sncosmo.model class. For example, sn_modeldir = + 'C:/Users/username/Documents/SALT3.NIR_WAVEEXT'. These data can be downloaded + from https://github.com/LSST-strong-lensing/data_public. For more detail, + please look at the documentation of RandomizedSupernovae class. Defaults to None. + """ + + # TODO: ADD EXCEPTION FOR DEFLECTOR AND SOURCE POP FILTER MISMATCH + super().__init__( + sky_area=sky_area, + cosmo=cosmo, + lightcurve_time=lightcurve_time, + sn_type=sn_type, + sn_absolute_mag_band=sn_absolute_mag_band, + sn_absolute_zpsys=sn_absolute_zpsys, + sn_modeldir=sn_modeldir, + ) + self.cosmo = cosmo + self._lens_galaxies = deflector_population + self._sources = source_population + + self._factor_source = self.sky_area.to_value( + "deg2" + ) / self._sources.sky_area.to_value("deg2") + self._factor_deflector = self.sky_area.to_value( + "deg2" + ) / self._lens_galaxies.sky_area.to_value("deg2") + self.los_config = los_config + if self.los_config is None: + self.los_config = LOSConfig() + + def select_lens_at_random(self, **kwargs_lens_cut): + """Draw a random lens within the cuts of the lens and source, with possible + additional cut in the lensing configuration. + + # TODO: make sure mass function is preserved, # as well as option to draw all + lenses within the cuts within the area + + :return: Lens() instance with parameters of the deflector and lens and source + light + """ + while True: + source = self._sources.draw_source() + lens = self._lens_galaxies.draw_deflector() + gg_lens = Lens( + deflector_dict=lens, + source_dict=source, + deflector_type=self._lens_galaxies.deflector_profile, + variability_model=self._sources.variability_model, + kwargs_variability=self._sources.kwargs_variability, + sn_type=self.sn_type, + sn_absolute_mag_band=self.sn_absolute_mag_band, + sn_absolute_zpsys=self.sn_absolute_zpsys, + cosmo=self.cosmo, + source_type=self._sources.source_type, + light_profile=self._sources.light_profile, + lightcurve_time=self.lightcurve_time, + los_config=self.los_config, + sn_modeldir=self.sn_modeldir, + agn_driving_variability_model=self._sources.agn_driving_variability_model, + agn_driving_kwargs_variability=self._sources.agn_driving_kwargs_variability, + ) + if gg_lens.validity_test(**kwargs_lens_cut): + return gg_lens + + @property + def deflector_number(self): + """Number of potential deflectors (meaning all objects with mass that are being + considered to have potential sources behind them) + + :return: number of potential deflectors + """ + return round(self._factor_deflector * self._lens_galaxies.deflector_number()) + + @property + def source_number(self): + """Number of sources that are being considered to be placed in the sky area + potentially aligned behind deflectors. + + :return: number of potential sources + """ + return round(self._factor_source * self._sources.source_number_selected) + + def get_num_sources_tested_mean(self, testarea): + """Compute the mean of source galaxies needed to be tested within the test area. + + num_sources_tested_mean/ testarea = num_sources/ sky_area; testarea is in units + of arcsec^2, f_sky is in units of deg^2. 1 deg^2 = 12960000 arcsec^2 + """ + num_sources = self.source_number + num_sources_tested_mean = (testarea * num_sources) / ( + 12960000 * self._factor_source * self._sources.sky_area.to_value("deg2") + ) + return num_sources_tested_mean + + def get_num_sources_tested(self, testarea=None, num_sources_tested_mean=None): + """Draw a realization of the expected distribution (Poisson) around the mean for + the number of source galaxies tested.""" + if num_sources_tested_mean is None: + num_sources_tested_mean = self.get_num_sources_tested_mean(testarea) + num_sources_range = np.random.poisson(lam=num_sources_tested_mean) + return num_sources_range + + def draw_population(self, kwargs_lens_cuts, speed_factor=1): + """Return full population list of all lenses within the area # TODO: need to + implement a version of it. (improve the algorithm) + + :param kwargs_lens_cuts: validity test keywords + :param speed_factor: factor by which the number of deflectors is decreased to + speed up the calculations. + :type kwargs_lens_cuts: dict + :return: List of Lens instances with parameters of the deflectors and lens and + source light. + :rtype: list + """ + + # Initialize an empty list to store the Lens instances + gg_lens_population = [] + # Estimate the number of lensing systems + num_lenses = self.deflector_number + # num_sources = self._source_galaxies.galaxies_number() + # print(num_sources_tested_mean) + # print("num_lenses is " + str(num_lenses)) + # print("num_sources is " + str(num_sources)) + # print(np.int(num_lenses * num_sources_tested_mean)) + + # Draw a population of galaxy-galaxy lenses within the area. + for _ in range(int(num_lenses / speed_factor)): + lens = self._lens_galaxies.draw_deflector() + test_area = draw_test_area(deflector=lens) + num_sources_tested = self.get_num_sources_tested( + testarea=test_area * speed_factor + ) + # TODO: to implement this for a multi-source plane lens system + if num_sources_tested > 0: + n = 0 + while n < num_sources_tested: + source = self._sources.draw_source() + gg_lens = Lens( + deflector_dict=lens, + source_dict=source, + deflector_type=self._lens_galaxies.deflector_profile, + variability_model=self._sources.variability_model, + kwargs_variability=self._sources.kwargs_variability, + sn_type=self.sn_type, + sn_absolute_mag_band=self.sn_absolute_mag_band, + sn_absolute_zpsys=self.sn_absolute_zpsys, + cosmo=self.cosmo, + test_area=test_area, + source_type=self._sources.source_type, + los_config=self.los_config, + light_profile=self._sources.light_profile, + lightcurve_time=self.lightcurve_time, + sn_modeldir=self.sn_modeldir, + agn_driving_variability_model=self._sources.agn_driving_variability_model, + agn_driving_kwargs_variability=self._sources.agn_driving_kwargs_variability, + ) + # Check the validity of the lens system + if gg_lens.validity_test(**kwargs_lens_cuts): + gg_lens_population.append(gg_lens) + # if a lens system passes the validity test, code should exit + # the loop. so, n should be greater or equal to + # num_sources_tested which will break the while loop + # (instead of this one can simply use break). + n = num_sources_tested + else: + n += 1 + return gg_lens_population + + +def draw_test_area(deflector): + """Draw a test area around the deflector. + + :param deflector: deflector dictionary + :return: test area in arcsec^2 + """ + theta_e_infinity = theta_e_when_source_infinity(deflector) + test_area = np.pi * (theta_e_infinity * 2.5) ** 2 + return test_area From 49d9633c7f0a0d8a4c20c5ac9153d69668fdb18a Mon Sep 17 00:00:00 2001 From: narayan Date: Tue, 8 Oct 2024 16:01:29 -0400 Subject: [PATCH 02/87] cleaned up FalsePositive class. --- slsim/Deflectors/elliptical_lens_galaxies.py | 2 +- slsim/false_positive.py | 423 +------------------ 2 files changed, 23 insertions(+), 402 deletions(-) diff --git a/slsim/Deflectors/elliptical_lens_galaxies.py b/slsim/Deflectors/elliptical_lens_galaxies.py index ce7bd23e5..6e402891a 100644 --- a/slsim/Deflectors/elliptical_lens_galaxies.py +++ b/slsim/Deflectors/elliptical_lens_galaxies.py @@ -97,7 +97,7 @@ def draw_deflector(self): deflector["vel_disp"] = vel_disp if deflector["e1_light"] == -1 or deflector["e2_light"] == -1: e1_light, e2_light, e1_mass, e2_mass = elliptical_projected_eccentricity( - **deflector, **self._kwargs_mass2light + **deflector ) deflector["e1_light"] = e1_light deflector["e2_light"] = e2_light diff --git a/slsim/false_positive.py b/slsim/false_positive.py index 0e42ac902..543308816 100644 --- a/slsim/false_positive.py +++ b/slsim/false_positive.py @@ -1,5 +1,3 @@ -import warnings - import numpy as np from lenstronomy.Analysis.lens_profile import LensProfileAnalysis from lenstronomy.Cosmo.lens_cosmo import LensCosmo @@ -10,15 +8,14 @@ ) from slsim.ParamDistributions.los_config import LOSConfig from slsim.Util.param_util import ellipticity_slsim_to_lenstronomy -from lenstronomy.LightModel.light_model import LightModel from lenstronomy.Util import constants -from lenstronomy.Util import data_util -from lenstronomy.Util import util -from slsim.lensed_system_base import LensedSystemBase +#from slsim.lensed_system_base import LensedSystemBase +from slsim.Sources.source import Source +from slsim.Deflectors.deflector import Deflector -class Lens(LensedSystemBase): +class FalsePositive(object): """Class to manage individual lenses.""" def __init__( @@ -28,24 +25,10 @@ def __init__( cosmo, deflector_type="EPL", source_type="extended", - lens_equation_solver="lenstronomy_analytical", - variability_model=None, - kwargs_variability=None, - sn_type=None, - sn_absolute_mag_band=None, - sn_absolute_zpsys=None, test_area=4 * np.pi, - mixgauss_means=None, - mixgauss_stds=None, - mixgauss_weights=None, - magnification_limit=0.01, light_profile="single_sersic", - lightcurve_time=None, los_config=None, - sn_modeldir=None, los_dict=None, - agn_driving_variability_model=None, - agn_driving_kwargs_variability=None, ): """ @@ -59,84 +42,29 @@ def __init__( :param source_type: type of the source 'extended' or 'point_source' or 'point_plus_extended' supported :type source_type: str - :param lens_equation_solver: type of lens equation solver; currently supporting - "lenstronomy_analytical" and "lenstronomy_general" - :type lens_equation_solver: str - :param variability_model: keyword for variability model to be used. This is an - input for the Variability class. - :type variability_model: str - :param kwargs_variability: keyword arguments for the variability of a source. - This is associated with an input for Variability class. - :type kwargs_variability: list of str - :param sn_type: Supernova type (Ia, Ib, Ic, IIP, etc.) - :type sn_type: str - :param sn_absolute_mag_band: Band used to normalize to absolute magnitude - :type sn_absolute_mag_band: str or `~sncosmo.Bandpass` - :param sn_absolute_zpsys: Optional, AB or Vega (AB default) - :type sn_absolute_zpsys: str - :param test_area: area of disk around one lensing galaxies to be investigated - on (in arc-seconds^2) - :param magnification_limit: absolute lensing magnification lower limit to - register a point source (ignore highly de-magnified images) - :type magnification_limit: float >= 0 - :param light_profile: keyword for number of sersic profile to use in source - light model - :type light_profile: str . Either "single_sersic" or "double_sersic" . - :param lightcurve_time: observation time array for lightcurve in unit of days. - :type lightcurve_time: array - :param sn_modeldir: sn_modeldir is the path to the directory containing files - needed to initialize the sncosmo.model class. For example, - sn_modeldir = 'C:/Users/username/Documents/SALT3.NIR_WAVEEXT'. These data can - be downloaded from https://github.com/LSST-strong-lensing/data_public . - For more detail, please look at the documentation of RandomizedSupernovae - class. - :type sn_modeldir: str - :param los_dict: line of sight dictionary (optional, takes these values instead of drawing from distribution) - Takes "gamma" = [gamma1, gamma2] and "kappa" = kappa as entries - :type los_dict: dict - :param agn_driving_variability_model: Variability model with light_curve output - which drives the variability across all bands of the agn. - :type agn_driving_variability_model: str (e.g. "light_curve", "sinusoidal", "bending_power_law") - :param agn_driving_kwargs_variability: Dictionary containing agn variability - parameters for the driving variability class. eg: variable_agn_kwarg_dict = - {"length_of_light_curve": 1000, "time_resolution": 1, - "log_breakpoint_frequency": 1 / 20, "low_frequency_slope": 1, - "high_frequency_slope": 3, "normal_magnitude_variance": 0.1}. For the detailed - explanation of these parameters, see generate_signal() function in - astro_util.py. - :type agn_driving_kwargs_variability: dict """ - super().__init__( + self.test_area = test_area + self.cosmo = cosmo + self._source_type = source_type + self.light_profile = light_profile + self.source = Source( source_dict=source_dict, - deflector_dict=deflector_dict, cosmo=cosmo, + variability_model=None, + kwargs_variability=None, + sn_type=None, + sn_absolute_mag_band=None, + sn_absolute_zpsys=None, + lightcurve_time=None, + sn_modeldir=None, + agn_driving_variability_model=None, + agn_driving_kwargs_variability=None, + ) + self.deflector = Deflector( deflector_type=deflector_type, - test_area=test_area, - variability_model=variability_model, - kwargs_variability=kwargs_variability, - lightcurve_time=lightcurve_time, - sn_type=sn_type, - sn_absolute_mag_band=sn_absolute_mag_band, - sn_absolute_zpsys=sn_absolute_zpsys, - sn_modeldir=sn_modeldir, - agn_driving_variability_model=agn_driving_variability_model, - agn_driving_kwargs_variability=agn_driving_kwargs_variability, + deflector_dict=deflector_dict, ) - self.cosmo = cosmo - self._source_type = source_type - self._lens_equation_solver = lens_equation_solver - self._magnification_limit = magnification_limit - self.kwargs_variab = kwargs_variability - self.light_profile = light_profile - - if self._source_type == "extended" and self.kwargs_variab is not None: - warning_msg = ( - "Extended source can not have variability. Therefore," - "variability information provided by you will not be used." - ) - warnings.warn(warning_msg, category=UserWarning, stacklevel=2) - self._lens_cosmo = LensCosmo( z_lens=float(self.deflector.redshift), z_source=float(self.source.redshift), @@ -150,13 +78,6 @@ def __init__( los_dict = {} self.los_config = LOSConfig(**los_dict) - @property - def image_number(self): - """Number of images in the lensing configuration. - - :return: number of images - """ - return len(self.point_source_image_positions()[0]) @property def deflector_position(self): @@ -196,118 +117,6 @@ def extended_source_image_positions(self): ) return self._image_positions - def point_source_image_positions(self): - """Returns point source image positions by solving the lens equation. In the - absence of a point source, this function returns the solution for the center of - the extended source. - - :return: x-pos, y-pos - """ - if not hasattr(self, "_point_image_positions"): - lens_model_list, kwargs_lens = self.deflector_mass_model_lenstronomy() - lens_model_class = LensModel(lens_model_list=lens_model_list) - lens_eq_solver = LensEquationSolver(lens_model_class) - point_source_pos_x, point_source_pos_y = self.source.point_source_position( - center_lens=self.deflector_position, draw_area=self.test_area - ) - # uses analytical lens equation solver in case it is supported by lenstronomy for speed-up - if ( - self._lens_equation_solver == "lenstronomy_analytical" - and analytical_lens_model_support(lens_model_list) is True - ): - solver = "analytical" - else: - solver = "lenstronomy" - self._point_image_positions = lens_eq_solver.image_position_from_source( - point_source_pos_x, - point_source_pos_y, - kwargs_lens, - solver=solver, - search_window=self.einstein_radius * 6, - min_distance=self.einstein_radius * 6 / 200, - magnification_limit=self._magnification_limit, - ) - return self._point_image_positions - - def validity_test( - self, - min_image_separation=0, - max_image_separation=10, - mag_arc_limit=None, - ): - """Check whether lensing configuration matches selection and plausibility - criteria. - - :param min_image_separation: minimum image separation - :param max_image_separation: maximum image separation - :param mag_arc_limit: dictionary with key of bands and values of magnitude - limits of integrated lensed arc - :type mag_arc_limit: dict with key of bands and values of magnitude limits - :return: boolean - """ - # Criteria 1:The redshift of the lens (z_lens) must be less than the - # redshift of the source (z_source). - z_lens = self.deflector.redshift - z_source = self.source.redshift - if z_lens >= z_source: - return False - - # Criteria 2: The angular Einstein radius of the lensing configuration (theta_E) - # times 2 must be greater than or equal to the minimum image separation - # (min_image_separation) and less than or equal to the maximum image - # separation (max_image_separation). - if not min_image_separation <= 2 * self.einstein_radius <= max_image_separation: - return False - - # Criteria 3: The distance between the lens center and the source position - # must be less than or equal to the angular Einstein radius - # of the lensing configuration (times sqrt(2)). - center_lens, center_source = ( - self.deflector_position, - self.source.point_source_position( - center_lens=self.deflector_position, draw_area=self.test_area - ), - ) - if np.sum((center_lens - center_source) ** 2) > self.einstein_radius**2 * 2: - return False - - # Criteria 4: The lensing configuration must produce at least two SL images. - image_positions = self.point_source_image_positions() - if len(image_positions[0]) < 2: - return False - - # Criteria 5: The maximum separation between any two image positions must be - # greater than or equal to the minimum image separation and less than or - # equal to the maximum image separation. - image_separation = image_separation_from_positions(image_positions) - if not min_image_separation <= image_separation <= max_image_separation: - return False - - # Criteria 6: (optional) - # compute the magnified brightness of the lensed extended arc for different - # bands at least in one band, the magnitude has to be brighter than the limit - if mag_arc_limit is not None and self._source_type in [ - "extended", - "point_plus_extended", - ]: - # makes sure magnification of extended source is only used when there is - # an extended source - bool_mag_limit = False - host_mag = self.extended_source_magnification() - for band, mag_limit_band in mag_arc_limit.items(): - mag_source = self.extended_source_magnitude(band) - mag_arc = mag_source - 2.5 * np.log10( - host_mag - ) # lensing magnification results in a shift in magnitude - if mag_arc < mag_limit_band: - bool_mag_limit = True - break - if bool_mag_limit is False: - return False - # TODO make similar criteria for point source magnitudes - return True - # TODO: test for signal-to-noise ratio in surface brightness - @property def deflector_redshift(self): """ @@ -427,89 +236,8 @@ def deflector_magnitude(self, band): """ return self.deflector.magnitude(band=band) - def point_source_arrival_times(self): - """Arrival time of images relative to a straight line without lensing. Negative - values correspond to images arriving earlier, and positive signs correspond to - images arriving later. - - :return: arrival times for each image [days] - :rtype: numpy array - """ - lens_model_list, kwargs_lens = self.deflector_mass_model_lenstronomy() - lens_model = LensModel( - lens_model_list=lens_model_list, - cosmo=self.cosmo, - z_lens=self.deflector_redshift, - z_source=self.source_redshift, - ) - x_image, y_image = self.point_source_image_positions() - arrival_times = lens_model.arrival_time( - x_image, y_image, kwargs_lens=kwargs_lens - ) - return arrival_times - - def image_observer_times(self, t_obs): - """Calculates time of the source at the different images, not correcting for - redshifts, but for time delays. The time is relative to the first arriving - image. - - :param t_obs: time of observation [days]. It could be a single observation time - or an array of observation time. - :return: time of the source when seen in the different images (without redshift - correction) - :rtype: numpy array. Each element of the array corresponds to different image - observation times. - """ - arrival_times = self.point_source_arrival_times() - if type(t_obs) is np.ndarray and len(t_obs) > 1: - observer_times = ( - t_obs[:, np.newaxis] - arrival_times + np.min(arrival_times) - ).T - else: - observer_times = (t_obs - arrival_times + np.min(arrival_times))[ - :, np.newaxis - ] - - return observer_times - - def point_source_magnitude(self, band, lensed=False, time=None): - """Point source magnitude, either unlensed (single value) or lensed (array) with - macro-model magnifications. - # TODO: time-variability with micro-lensing - - :param band: imaging band - :type band: string - :param lensed: if True, returns the lensed magnified magnitude - :type lensed: bool - :param time: time is a image observation time in units of days. If None, - provides magnitude without variability. - :return: point source magnitude - """ - # TODO: might have to change conventions between extended and point source - if lensed: - magnif = self.point_source_magnification() - magnif_log = 2.5 * np.log10(abs(magnif)) - if time is not None: - time = time - image_observed_times = self.image_observer_times(time) - variable_magnitude = self.source.point_source_magnitude( - band, - image_observation_times=image_observed_times, - ) - lensed_variable_magnitude = ( - variable_magnitude - magnif_log[:, np.newaxis] - ) - return lensed_variable_magnitude - else: - source_mag_unlensed = self.source.point_source_magnitude(band) - magnified_mag_list = [] - for i in range(len(magnif_log)): - magnified_mag_list.append(source_mag_unlensed - magnif_log[i]) - return np.array(magnified_mag_list) - return self.source.point_source_magnitude(band) - - def extended_source_magnitude(self, band, lensed=False): + def extended_source_magnitude(self, band): """Unlensed apparent magnitude of the extended source for a given band (assumes that size is the same for different bands) @@ -522,65 +250,8 @@ def extended_source_magnitude(self, band, lensed=False): # band_string = str("mag_" + band) # TODO: might have to change conventions between extended and point source source_mag = self.source.extended_source_magnitude(band) - if lensed: - mag = self.extended_source_magnification() - return source_mag - 2.5 * np.log10(mag) return source_mag - def point_source_magnification(self): - """Macro-model magnification of point sources. - - :return: signed magnification of point sources in same order as image positions - """ - if not hasattr(self, "_ps_magnification"): - lens_model_list, kwargs_lens = self.deflector_mass_model_lenstronomy() - lensModel = LensModel(lens_model_list=lens_model_list) - img_x, img_y = self.point_source_image_positions() - self._ps_magnification = lensModel.magnification(img_x, img_y, kwargs_lens) - return self._ps_magnification - - def extended_source_magnification(self): - """Compute the extended lensed surface brightness and calculates the integrated - flux-weighted magnification factor of the extended host galaxy. - - :return: integrated magnification factor of host magnitude - """ - if not hasattr(self, "_extended_source_magnification"): - kwargs_model, kwargs_params = self.lenstronomy_kwargs(band=None) - lightModel = LightModel( - light_model_list=kwargs_model.get("source_light_model_list", []) - ) - lensModel = LensModel( - lens_model_list=kwargs_model.get("lens_model_list", []) - ) - theta_E = self.einstein_radius - center_source = self.source.extended_source_position( - center_lens=self.deflector_position, draw_area=self.test_area - ) - - kwargs_source_mag = kwargs_params["kwargs_source"] - kwargs_source_amp = data_util.magnitude2amplitude( - lightModel, kwargs_source_mag, magnitude_zero_point=0 - ) - - num_pix = 200 - delta_pix = theta_E * 4 / num_pix - x, y = util.make_grid(numPix=num_pix, deltapix=delta_pix) - x += center_source[0] - y += center_source[1] - beta_x, beta_y = lensModel.ray_shooting(x, y, kwargs_params["kwargs_lens"]) - flux_lensed = np.sum( - lightModel.surface_brightness(beta_x, beta_y, kwargs_source_amp) - ) - flux_no_lens = np.sum( - lightModel.surface_brightness(x, y, kwargs_source_amp) - ) - if flux_no_lens > 0: - self._extended_source_magnification = flux_lensed / flux_no_lens - else: - self._extended_source_magnification = 0 - return self._extended_source_magnification - def lenstronomy_kwargs(self, band=None): """Generates lenstronomy dictionary conventions for the class object. @@ -707,56 +378,6 @@ def source_light_model_lenstronomy(self, band=None): all_source_kwarg_dict["kwargs_ps"] = kwargs_ps return source_models, all_source_kwarg_dict - def kappa_star(self, ra, dec): - """Computes the stellar surface density at location (ra, dec) in units of - lensing convergence. - - :param ra: position in the image plane - :param dec: position in the image plane - :return: kappa_star - """ - stellar_mass = self.deflector_stellar_mass() - kwargs_model, kwargs_params = self.lenstronomy_kwargs(band=None) - lightModel = LightModel( - light_model_list=kwargs_model.get("lens_light_model_list", []) - ) - kwargs_lens_light_mag = kwargs_params["kwargs_lens_light"] - kwargs_lens_light_amp = data_util.magnitude2amplitude( - lightModel, kwargs_lens_light_mag, magnitude_zero_point=0 - ) - - total_flux = lightModel.total_flux(kwargs_lens_light_amp) # integrated flux - flux_local = lightModel.surface_brightness( - ra, dec, kwargs_lens_light_amp - ) # surface brightness per arcsecond square - kappa_star = ( - flux_local / total_flux * stellar_mass / self._lens_cosmo.sigma_crit_angle - ) - return kappa_star - - -def image_separation_from_positions(image_positions): - """Calculate image separation in arc-seconds; if there are only two images, the - separation between them is returned; if there are more than 2 images, the maximum - separation is returned. - - :param image_positions: list of image positions in arc-seconds - :return: image separation in arc-seconds - """ - if len(image_positions[0]) == 2: - image_separation = np.sqrt( - (image_positions[0][0] - image_positions[0][1]) ** 2 - + (image_positions[1][0] - image_positions[1][1]) ** 2 - ) - else: - coords = np.stack((image_positions[0], image_positions[1]), axis=-1) - separations = np.sqrt( - np.sum((coords[:, np.newaxis] - coords[np.newaxis, :]) ** 2, axis=-1) - ) - image_separation = np.max(separations) - return image_separation - - def theta_e_when_source_infinity(deflector_dict=None, v_sigma=None): """Calculate Einstein radius in arc-seconds for a source at infinity. From ddf9654e5e92ef9a5924859f704bb7fdfb5373d1 Mon Sep 17 00:00:00 2001 From: narayan Date: Tue, 8 Oct 2024 16:27:18 -0400 Subject: [PATCH 03/87] cleaned up FalsePositivePop class. --- slsim/false_positive_pop.py | 58 ++++++++----------------------------- 1 file changed, 12 insertions(+), 46 deletions(-) diff --git a/slsim/false_positive_pop.py b/slsim/false_positive_pop.py index 9c220c350..455a56b77 100644 --- a/slsim/false_positive_pop.py +++ b/slsim/false_positive_pop.py @@ -1,7 +1,7 @@ import sncosmo import numpy as np -from slsim.false_positive import Lens +from slsim.false_positive import FalsePositive from typing import Optional, Union from astropy.cosmology import Cosmology from slsim.lens import theta_e_when_source_infinity @@ -11,21 +11,16 @@ from slsim.lensed_population_base import LensedPopulationBase -class LensPop(LensedPopulationBase): +class FalsePositivePop(LensedPopulationBase): """Class to perform samples of lens population.""" def __init__( self, - deflector_population: DeflectorsBase, - source_population: SourcePopBase, + elliptical_galaxy_population: DeflectorsBase, + blue_galaxy_population: SourcePopBase, cosmo: Optional[Cosmology] = None, sky_area: Optional[float] = None, - lightcurve_time: Optional[np.ndarray] = None, - sn_type: Optional[str] = None, - sn_absolute_mag_band: Optional[Union[str, sncosmo.Bandpass]] = None, - sn_absolute_zpsys: Optional[str] = None, los_config: Optional[LOSConfig] = None, - sn_modeldir: Optional[str] = None, ): """ Args: @@ -33,40 +28,21 @@ def __init__( source_population (SourcePopBase): Source population as an instance of a SourcePopBase subclass cosmo (Optional[Cosmology], optional): AstroPy Cosmology instance. If None, defaults to flat LCDM with h0=0.7 and Om0=0.3. Defaults to None. - lightcurve_time (Optional[np.ndarray], optional): Lightcurve observation time array in units of days. Defaults to None. - sn_type (Optional[str], optional): Supernova type (Ia, Ib, Ic, IIP, etc.). Defaults to None. - sn_absolute_mag_band (Optional[Union[str,sncosmo.Bandpass]], optional): Band used to normalize to absolute magnitude. - Defaults to None. - sn_absolute_zpsys (Optional[str], optional): Zero point system, either AB or Vega, with None defaulting to AB. - Defaults to None. los_config (Optional[LOSConfig], optional): Configuration for line of sight distribution. Defaults to None. - sn_modeldir (Optional[str], optional): sn_modeldir is the path to the directory containing files needed to initialize - the sncosmo.model class. For example, sn_modeldir = - 'C:/Users/username/Documents/SALT3.NIR_WAVEEXT'. These data can be downloaded - from https://github.com/LSST-strong-lensing/data_public. For more detail, - please look at the documentation of RandomizedSupernovae class. Defaults to None. """ # TODO: ADD EXCEPTION FOR DEFLECTOR AND SOURCE POP FILTER MISMATCH - super().__init__( - sky_area=sky_area, - cosmo=cosmo, - lightcurve_time=lightcurve_time, - sn_type=sn_type, - sn_absolute_mag_band=sn_absolute_mag_band, - sn_absolute_zpsys=sn_absolute_zpsys, - sn_modeldir=sn_modeldir, - ) self.cosmo = cosmo - self._lens_galaxies = deflector_population - self._sources = source_population - - self._factor_source = self.sky_area.to_value( + self._lens_galaxies = elliptical_galaxy_population + self._sources = blue_galaxy_population + self.sky_area = sky_area + + """self._factor_source = self.sky_area.to_value( "deg2" ) / self._sources.sky_area.to_value("deg2") self._factor_deflector = self.sky_area.to_value( "deg2" - ) / self._lens_galaxies.sky_area.to_value("deg2") + ) / self._lens_galaxies.sky_area.to_value("deg2")""" self.los_config = los_config if self.los_config is None: self.los_config = LOSConfig() @@ -84,23 +60,13 @@ def select_lens_at_random(self, **kwargs_lens_cut): while True: source = self._sources.draw_source() lens = self._lens_galaxies.draw_deflector() - gg_lens = Lens( + gg_lens = FalsePositive( deflector_dict=lens, source_dict=source, deflector_type=self._lens_galaxies.deflector_profile, - variability_model=self._sources.variability_model, - kwargs_variability=self._sources.kwargs_variability, - sn_type=self.sn_type, - sn_absolute_mag_band=self.sn_absolute_mag_band, - sn_absolute_zpsys=self.sn_absolute_zpsys, cosmo=self.cosmo, source_type=self._sources.source_type, - light_profile=self._sources.light_profile, - lightcurve_time=self.lightcurve_time, - los_config=self.los_config, - sn_modeldir=self.sn_modeldir, - agn_driving_variability_model=self._sources.agn_driving_variability_model, - agn_driving_kwargs_variability=self._sources.agn_driving_kwargs_variability, + light_profile=self._sources.light_profile ) if gg_lens.validity_test(**kwargs_lens_cut): return gg_lens From e202f7979122577ad5f04bad464209735daa391d Mon Sep 17 00:00:00 2001 From: narayan Date: Tue, 8 Oct 2024 17:09:33 -0400 Subject: [PATCH 04/87] added a z_min parameter in source_draw() in Galaxies class. --- slsim/Sources/galaxies.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/slsim/Sources/galaxies.py b/slsim/Sources/galaxies.py index 3febc87e0..620dda7bc 100644 --- a/slsim/Sources/galaxies.py +++ b/slsim/Sources/galaxies.py @@ -120,14 +120,20 @@ def source_number_selected(self): """ return self._num_select - def draw_source(self): + def draw_source(self, z_min=None): """Choose source at random. + :param z_min: minimum redshift for source to be drawn. :return: dictionary of source """ - - index = random.randint(0, self._num_select - 1) - galaxy = self._galaxy_select[index] + if z_min is not None: + filtered_galaxies = self._galaxy_select[ + self._galaxy_select['z'] > z_min] + index = random.randint(0, len(filtered_galaxies) -1) + galaxy = filtered_galaxies[index] + else: + index = random.randint(0, self._num_select - 1) + galaxy = self._galaxy_select[index] if "a_rot" in galaxy.colnames: phi_rot = galaxy["a_rot"] else: From 61be7838c4057eedd99878fe6ed0fe9924ce1371 Mon Sep 17 00:00:00 2001 From: narayan Date: Tue, 8 Oct 2024 17:52:35 -0400 Subject: [PATCH 05/87] cleaned up FalsePositivePop class --- slsim/false_positive_pop.py | 167 +++++------------------------------- 1 file changed, 22 insertions(+), 145 deletions(-) diff --git a/slsim/false_positive_pop.py b/slsim/false_positive_pop.py index 455a56b77..b6121a586 100644 --- a/slsim/false_positive_pop.py +++ b/slsim/false_positive_pop.py @@ -1,25 +1,23 @@ -import sncosmo import numpy as np from slsim.false_positive import FalsePositive -from typing import Optional, Union +from typing import Optional from astropy.cosmology import Cosmology from slsim.lens import theta_e_when_source_infinity from slsim.Sources.source_pop_base import SourcePopBase from slsim.ParamDistributions.los_config import LOSConfig from slsim.Deflectors.deflectors_base import DeflectorsBase -from slsim.lensed_population_base import LensedPopulationBase +from slsim.lens_pop import draw_test_area -class FalsePositivePop(LensedPopulationBase): - """Class to perform samples of lens population.""" +class FalsePositivePop(object): + """Class to perform samples of false positive population.""" def __init__( self, elliptical_galaxy_population: DeflectorsBase, blue_galaxy_population: SourcePopBase, cosmo: Optional[Cosmology] = None, - sky_area: Optional[float] = None, los_config: Optional[LOSConfig] = None, ): """ @@ -31,157 +29,36 @@ def __init__( los_config (Optional[LOSConfig], optional): Configuration for line of sight distribution. Defaults to None. """ - # TODO: ADD EXCEPTION FOR DEFLECTOR AND SOURCE POP FILTER MISMATCH self.cosmo = cosmo self._lens_galaxies = elliptical_galaxy_population self._sources = blue_galaxy_population - self.sky_area = sky_area - - """self._factor_source = self.sky_area.to_value( - "deg2" - ) / self._sources.sky_area.to_value("deg2") - self._factor_deflector = self.sky_area.to_value( - "deg2" - ) / self._lens_galaxies.sky_area.to_value("deg2")""" self.los_config = los_config if self.los_config is None: self.los_config = LOSConfig() - def select_lens_at_random(self, **kwargs_lens_cut): - """Draw a random lens within the cuts of the lens and source, with possible - additional cut in the lensing configuration. - - # TODO: make sure mass function is preserved, # as well as option to draw all - lenses within the cuts within the area - - :return: Lens() instance with parameters of the deflector and lens and source - light + def draw_false_positive(self, number=1): + """Draw given number of false positive within the cuts of the lens and source. + :return: list of FalsePositive() instance with parameters of a pair of elliptical + and blue galaxy. """ - while True: - source = self._sources.draw_source() + false_positive_population = [] + for _ in range(number): lens = self._lens_galaxies.draw_deflector() - gg_lens = FalsePositive( + tolerance = 0.002 + z_min=lens["z"] + tolerance + source = self._sources.draw_source(z_min=z_min) + test_area = draw_test_area(deflector=lens) + false_positive = FalsePositive( deflector_dict=lens, source_dict=source, deflector_type=self._lens_galaxies.deflector_profile, cosmo=self.cosmo, source_type=self._sources.source_type, - light_profile=self._sources.light_profile - ) - if gg_lens.validity_test(**kwargs_lens_cut): - return gg_lens - - @property - def deflector_number(self): - """Number of potential deflectors (meaning all objects with mass that are being - considered to have potential sources behind them) - - :return: number of potential deflectors - """ - return round(self._factor_deflector * self._lens_galaxies.deflector_number()) - - @property - def source_number(self): - """Number of sources that are being considered to be placed in the sky area - potentially aligned behind deflectors. - - :return: number of potential sources - """ - return round(self._factor_source * self._sources.source_number_selected) - - def get_num_sources_tested_mean(self, testarea): - """Compute the mean of source galaxies needed to be tested within the test area. - - num_sources_tested_mean/ testarea = num_sources/ sky_area; testarea is in units - of arcsec^2, f_sky is in units of deg^2. 1 deg^2 = 12960000 arcsec^2 - """ - num_sources = self.source_number - num_sources_tested_mean = (testarea * num_sources) / ( - 12960000 * self._factor_source * self._sources.sky_area.to_value("deg2") - ) - return num_sources_tested_mean - - def get_num_sources_tested(self, testarea=None, num_sources_tested_mean=None): - """Draw a realization of the expected distribution (Poisson) around the mean for - the number of source galaxies tested.""" - if num_sources_tested_mean is None: - num_sources_tested_mean = self.get_num_sources_tested_mean(testarea) - num_sources_range = np.random.poisson(lam=num_sources_tested_mean) - return num_sources_range - - def draw_population(self, kwargs_lens_cuts, speed_factor=1): - """Return full population list of all lenses within the area # TODO: need to - implement a version of it. (improve the algorithm) - - :param kwargs_lens_cuts: validity test keywords - :param speed_factor: factor by which the number of deflectors is decreased to - speed up the calculations. - :type kwargs_lens_cuts: dict - :return: List of Lens instances with parameters of the deflectors and lens and - source light. - :rtype: list - """ - - # Initialize an empty list to store the Lens instances - gg_lens_population = [] - # Estimate the number of lensing systems - num_lenses = self.deflector_number - # num_sources = self._source_galaxies.galaxies_number() - # print(num_sources_tested_mean) - # print("num_lenses is " + str(num_lenses)) - # print("num_sources is " + str(num_sources)) - # print(np.int(num_lenses * num_sources_tested_mean)) - - # Draw a population of galaxy-galaxy lenses within the area. - for _ in range(int(num_lenses / speed_factor)): - lens = self._lens_galaxies.draw_deflector() - test_area = draw_test_area(deflector=lens) - num_sources_tested = self.get_num_sources_tested( - testarea=test_area * speed_factor + light_profile=self._sources.light_profile, + test_area=test_area ) - # TODO: to implement this for a multi-source plane lens system - if num_sources_tested > 0: - n = 0 - while n < num_sources_tested: - source = self._sources.draw_source() - gg_lens = Lens( - deflector_dict=lens, - source_dict=source, - deflector_type=self._lens_galaxies.deflector_profile, - variability_model=self._sources.variability_model, - kwargs_variability=self._sources.kwargs_variability, - sn_type=self.sn_type, - sn_absolute_mag_band=self.sn_absolute_mag_band, - sn_absolute_zpsys=self.sn_absolute_zpsys, - cosmo=self.cosmo, - test_area=test_area, - source_type=self._sources.source_type, - los_config=self.los_config, - light_profile=self._sources.light_profile, - lightcurve_time=self.lightcurve_time, - sn_modeldir=self.sn_modeldir, - agn_driving_variability_model=self._sources.agn_driving_variability_model, - agn_driving_kwargs_variability=self._sources.agn_driving_kwargs_variability, - ) - # Check the validity of the lens system - if gg_lens.validity_test(**kwargs_lens_cuts): - gg_lens_population.append(gg_lens) - # if a lens system passes the validity test, code should exit - # the loop. so, n should be greater or equal to - # num_sources_tested which will break the while loop - # (instead of this one can simply use break). - n = num_sources_tested - else: - n += 1 - return gg_lens_population - - -def draw_test_area(deflector): - """Draw a test area around the deflector. - - :param deflector: deflector dictionary - :return: test area in arcsec^2 - """ - theta_e_infinity = theta_e_when_source_infinity(deflector) - test_area = np.pi * (theta_e_infinity * 2.5) ** 2 - return test_area + false_positive_population.append(false_positive) + if number == 1: + return false_positive_population[0] + else: + return false_positive_population \ No newline at end of file From 3ad8955200a4575be9a55a06adc3e3fb24de2ee3 Mon Sep 17 00:00:00 2001 From: narayan Date: Tue, 8 Oct 2024 18:01:50 -0400 Subject: [PATCH 06/87] minor change --- slsim/Sources/galaxies.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/slsim/Sources/galaxies.py b/slsim/Sources/galaxies.py index 620dda7bc..1e3571e4c 100644 --- a/slsim/Sources/galaxies.py +++ b/slsim/Sources/galaxies.py @@ -120,15 +120,15 @@ def source_number_selected(self): """ return self._num_select - def draw_source(self, z_min=None): + def draw_source(self, z_max=None): """Choose source at random. - :param z_min: minimum redshift for source to be drawn. + :param z_max: maximum redshift for source to be drawn. :return: dictionary of source """ - if z_min is not None: + if z_max is not None: filtered_galaxies = self._galaxy_select[ - self._galaxy_select['z'] > z_min] + self._galaxy_select['z'] < z_max] index = random.randint(0, len(filtered_galaxies) -1) galaxy = filtered_galaxies[index] else: From 63536dd235f1f85ba71130c4e025b13c6c9107d9 Mon Sep 17 00:00:00 2001 From: narayan Date: Wed, 9 Oct 2024 11:15:44 -0400 Subject: [PATCH 07/87] cleaned up few minor thing in false positive --- slsim/Sources/galaxies.py | 7 +++-- slsim/false_positive.py | 36 +-------------------- slsim/false_positive_pop.py | 63 ++++++++++++++++++++++++------------- 3 files changed, 47 insertions(+), 59 deletions(-) diff --git a/slsim/Sources/galaxies.py b/slsim/Sources/galaxies.py index 1e3571e4c..a857baf96 100644 --- a/slsim/Sources/galaxies.py +++ b/slsim/Sources/galaxies.py @@ -129,8 +129,11 @@ def draw_source(self, z_max=None): if z_max is not None: filtered_galaxies = self._galaxy_select[ self._galaxy_select['z'] < z_max] - index = random.randint(0, len(filtered_galaxies) -1) - galaxy = filtered_galaxies[index] + if len(filtered_galaxies) > 0: + index = random.randint(0, len(filtered_galaxies) -1) + galaxy = filtered_galaxies[index] + else: + raise ValueError(f"No galaxies found with z < {z_max}.") else: index = random.randint(0, self._num_select - 1) galaxy = self._galaxy_select[index] diff --git a/slsim/false_positive.py b/slsim/false_positive.py index 543308816..bc6576934 100644 --- a/slsim/false_positive.py +++ b/slsim/false_positive.py @@ -2,10 +2,6 @@ from lenstronomy.Analysis.lens_profile import LensProfileAnalysis from lenstronomy.Cosmo.lens_cosmo import LensCosmo from lenstronomy.LensModel.lens_model import LensModel -from lenstronomy.LensModel.Solver.lens_equation_solver import LensEquationSolver -from lenstronomy.LensModel.Solver.lens_equation_solver import ( - analytical_lens_model_support, -) from slsim.ParamDistributions.los_config import LOSConfig from slsim.Util.param_util import ellipticity_slsim_to_lenstronomy from lenstronomy.Util import constants @@ -86,37 +82,7 @@ def deflector_position(self): :return: [x_pox, y_pos] in arc seconds """ return self.deflector.deflector_center - - def extended_source_image_positions(self): - """Returns extended source image positions by solving the lens equation. - - :return: x-pos, y-pos - """ - if not hasattr(self, "_image_positions"): - lens_model_list, kwargs_lens = self.deflector_mass_model_lenstronomy() - lens_model_class = LensModel(lens_model_list=lens_model_list) - lens_eq_solver = LensEquationSolver(lens_model_class) - source_pos_x, source_pos_y = self.source.extended_source_position( - center_lens=self.deflector_position, draw_area=self.test_area - ) - if ( - self._lens_equation_solver == "lenstronomy_analytical" - and analytical_lens_model_support(lens_model_list) is True - ): - solver = "analytical" - else: - solver = "lenstronomy" - self._image_positions = lens_eq_solver.image_position_from_source( - source_pos_x, - source_pos_y, - kwargs_lens, - solver=solver, - search_window=self.einstein_radius * 6, - min_distance=self.einstein_radius * 6 / 200, - magnification_limit=self._magnification_limit, - ) - return self._image_positions - + @property def deflector_redshift(self): """ diff --git a/slsim/false_positive_pop.py b/slsim/false_positive_pop.py index b6121a586..cd87fb2d0 100644 --- a/slsim/false_positive_pop.py +++ b/slsim/false_positive_pop.py @@ -1,9 +1,6 @@ -import numpy as np - from slsim.false_positive import FalsePositive from typing import Optional from astropy.cosmology import Cosmology -from slsim.lens import theta_e_when_source_infinity from slsim.Sources.source_pop_base import SourcePopBase from slsim.ParamDistributions.los_config import LOSConfig from slsim.Deflectors.deflectors_base import DeflectorsBase @@ -37,28 +34,50 @@ def __init__( self.los_config = LOSConfig() def draw_false_positive(self, number=1): - """Draw given number of false positive within the cuts of the lens and source. - :return: list of FalsePositive() instance with parameters of a pair of elliptical - and blue galaxy. + """Draw given number of false positives within the cuts of the lens and source. + + :param number: number of false positive requested. The default value is 1. + :return: list of FalsePositive() instance with parameters of a pair of + elliptical and blue galaxy. """ false_positive_population = [] + for _ in range(number): - lens = self._lens_galaxies.draw_deflector() - tolerance = 0.002 - z_min=lens["z"] + tolerance - source = self._sources.draw_source(z_min=z_min) - test_area = draw_test_area(deflector=lens) - false_positive = FalsePositive( - deflector_dict=lens, - source_dict=source, - deflector_type=self._lens_galaxies.deflector_profile, - cosmo=self.cosmo, - source_type=self._sources.source_type, - light_profile=self._sources.light_profile, - test_area=test_area - ) - false_positive_population.append(false_positive) + successful = False + while not successful: + try: + # Sample a lens (deflector) + lens = self._lens_galaxies.draw_deflector() + tolerance = 0.002 + z_max = lens["z"] + tolerance + + # Try to draw a source with the z_max based on the lens redshift + source = self._sources.draw_source(z_max=z_max) + + # Compute test area for false positive position. + # This area will be used to determine the position of false positive. + test_area = 3 * draw_test_area(deflector=lens) + + # Create a FalsePositive instance with the lens and source information + false_positive = FalsePositive( + deflector_dict=lens, + source_dict=source, + deflector_type=self._lens_galaxies.deflector_profile, + cosmo=self.cosmo, + source_type=self._sources.source_type, + light_profile=self._sources.light_profile, + test_area=test_area + ) + + # Add the false positive to the population + false_positive_population.append(false_positive) + successful = True + + except ValueError as e: + # Handle the specific case where no sources are found for z_max + if str(e).startswith("No galaxies found"): + continue if number == 1: return false_positive_population[0] else: - return false_positive_population \ No newline at end of file + return false_positive_population From 4b862c826e5beb82c5ec861f599949e9ddcc9f9a Mon Sep 17 00:00:00 2001 From: narayan Date: Thu, 10 Oct 2024 09:31:46 -0400 Subject: [PATCH 08/87] Minor change in false positive --- slsim/false_positive.py | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/slsim/false_positive.py b/slsim/false_positive.py index bc6576934..5f174d8fd 100644 --- a/slsim/false_positive.py +++ b/slsim/false_positive.py @@ -19,7 +19,6 @@ def __init__( source_dict, deflector_dict, cosmo, - deflector_type="EPL", source_type="extended", test_area=4 * np.pi, light_profile="single_sersic", @@ -43,23 +42,16 @@ def __init__( self.cosmo = cosmo self._source_type = source_type self.light_profile = light_profile - self.source = Source( + """self.source = Source( source_dict=source_dict, - cosmo=cosmo, - variability_model=None, - kwargs_variability=None, - sn_type=None, - sn_absolute_mag_band=None, - sn_absolute_zpsys=None, - lightcurve_time=None, - sn_modeldir=None, - agn_driving_variability_model=None, - agn_driving_kwargs_variability=None, - ) - self.deflector = Deflector( + cosmo=cosmo + )""" + """self.deflector = Deflector( deflector_type=deflector_type, deflector_dict=deflector_dict, - ) + )""" + self.deflector = deflector_dict + self.source = source_dict self._lens_cosmo = LensCosmo( z_lens=float(self.deflector.redshift), From b97c7ded5c091bb0fa398dfc483f86b47f342c62 Mon Sep 17 00:00:00 2001 From: narayan Date: Thu, 10 Oct 2024 11:46:42 -0400 Subject: [PATCH 09/87] minor changes in Source and Lens class input. --- slsim/Sources/source.py | 22 +++++++++++++++++----- slsim/false_positive.py | 24 +++++++++++------------- slsim/false_positive_pop.py | 17 +++++++++++------ slsim/lens.py | 4 +++- slsim/lensed_system_base.py | 10 ++++++++++ 5 files changed, 52 insertions(+), 25 deletions(-) diff --git a/slsim/Sources/source.py b/slsim/Sources/source.py index 2f3a02ed9..ef42acd09 100644 --- a/slsim/Sources/source.py +++ b/slsim/Sources/source.py @@ -32,6 +32,8 @@ def __init__( agn_known_mag=None, agn_driving_variability_model=None, agn_driving_kwargs_variability=None, + source_type="extended", + light_profile="single_sersic" ): """ :param source_dict: Source properties @@ -70,6 +72,12 @@ def __init__( :param agn_driving_kwargs_variability: Dictionary containing all variability parameters for the driving variability class :type agn_driving_kwargs_variability: dict + :param source_type: type of the source 'extended' or 'point_source' or + 'point_plus_extended' supported + :type source_type: str + :param light_profile: keyword for number of sersic profile to use in source + light model + :type light_profile: str . Either "single_sersic" or "double_sersic" . """ # Convert dict to astropy table @@ -99,6 +107,8 @@ def __init__( self.agn_known_mag = agn_known_mag self.agn_driving_variability_model = agn_driving_variability_model self.agn_driving_kwargs_variability = agn_driving_kwargs_variability + self.source_type = source_type + self.light_profile = light_profile @property def kwargs_variability_extracted(self): @@ -484,14 +494,16 @@ def point_source_position(self, center_lens, draw_area): return extended_source_center def kwargs_extended_source_light( - self, center_lens, draw_area, band=None, light_profile_str="single_sersic" + self, center_lens, draw_area, band=None ): """Provides dictionary of keywords for the source light model(s). Kewords used are in lenstronomy conventions. + :param center_lens: center of the deflector. + Eg: np.array([center_x_lens, center_y_lens]) + :param draw_area: The area of the test region from which we randomly draw a + source position. Eg: 4*pi. :param band: Imaging band - :param light_profile_str: number of light_profile - :type light_profile_str: str . eg: "single_sersic" or "double_sersic". :return: dictionary of keywords for the source light model(s) """ if band is None: @@ -501,7 +513,7 @@ def kwargs_extended_source_light( center_source = self.extended_source_position( center_lens=center_lens, draw_area=draw_area ) - if light_profile_str == "single_sersic": + if self.light_profile == "single_sersic": size_source_arcsec = float(self.angular_size) e1_light_source_lenstronomy, e2_light_source_lenstronomy = ( ellipticity_slsim_to_lenstronomy( @@ -519,7 +531,7 @@ def kwargs_extended_source_light( "center_y": center_source[1], } ] - elif light_profile_str == "double_sersic": + elif self.light_profile == "double_sersic": # w0 and w1 are the weight of the n=1 and n=4 sersic component. if "w0" in self.source_dict.colnames or "w1" in self.source_dict.colnames: w0 = self.source_dict["w0"] diff --git a/slsim/false_positive.py b/slsim/false_positive.py index 5f174d8fd..509b5920b 100644 --- a/slsim/false_positive.py +++ b/slsim/false_positive.py @@ -7,17 +7,17 @@ from lenstronomy.Util import constants #from slsim.lensed_system_base import LensedSystemBase -from slsim.Sources.source import Source -from slsim.Deflectors.deflector import Deflector +#from slsim.Sources.source import Source +#from slsim.Deflectors.deflector import Deflector class FalsePositive(object): - """Class to manage individual lenses.""" + """Class to manage individual false positive.""" def __init__( self, - source_dict, - deflector_dict, + source_class, + deflector_class, cosmo, source_type="extended", test_area=4 * np.pi, @@ -27,13 +27,11 @@ def __init__( ): """ - :param source_dict: source properties - :type source_dict: dict or astropy table - :param deflector_dict: deflector properties - :type deflector_dict: dict + :param source_class: source class instance + :type source_class: Source class instance from slsim.Sources.source + :param deflector_dict: deflector instance + :type deflector_dict: Deflector class instance from slsim.Deflectors.deflector :param cosmo: astropy.cosmology instance - :param deflector_type: type of deflector, i.e. "EPL", "NFW_HERNQUIST", "NFW_CLUSTER" - :type deflector_type: str :param source_type: type of the source 'extended' or 'point_source' or 'point_plus_extended' supported :type source_type: str @@ -50,8 +48,8 @@ def __init__( deflector_type=deflector_type, deflector_dict=deflector_dict, )""" - self.deflector = deflector_dict - self.source = source_dict + self.deflector = deflector_class + self.source = source_class self._lens_cosmo = LensCosmo( z_lens=float(self.deflector.redshift), diff --git a/slsim/false_positive_pop.py b/slsim/false_positive_pop.py index cd87fb2d0..a9aa04ab5 100644 --- a/slsim/false_positive_pop.py +++ b/slsim/false_positive_pop.py @@ -4,6 +4,8 @@ from slsim.Sources.source_pop_base import SourcePopBase from slsim.ParamDistributions.los_config import LOSConfig from slsim.Deflectors.deflectors_base import DeflectorsBase +from slsim.Sources.source import Source +from slsim.Deflectors.deflector import Deflector from slsim.lens_pop import draw_test_area @@ -48,21 +50,24 @@ def draw_false_positive(self, number=1): try: # Sample a lens (deflector) lens = self._lens_galaxies.draw_deflector() + _lens = Deflector( + deflector_type=self._lens_galaxies.deflector_profile, + deflector_dict=lens) tolerance = 0.002 - z_max = lens["z"] + tolerance - + z_max = _lens.redshift + tolerance # Try to draw a source with the z_max based on the lens redshift source = self._sources.draw_source(z_max=z_max) - + _source = Source( + source_dict=source, + cosmo=self.cosmo) # Compute test area for false positive position. # This area will be used to determine the position of false positive. test_area = 3 * draw_test_area(deflector=lens) # Create a FalsePositive instance with the lens and source information false_positive = FalsePositive( - deflector_dict=lens, - source_dict=source, - deflector_type=self._lens_galaxies.deflector_profile, + deflector_class=_lens, + source_class=_source, cosmo=self.cosmo, source_type=self._sources.source_type, light_profile=self._sources.light_profile, diff --git a/slsim/lens.py b/slsim/lens.py index e43f6feb3..fee130fb2 100644 --- a/slsim/lens.py +++ b/slsim/lens.py @@ -121,10 +121,12 @@ def __init__( sn_modeldir=sn_modeldir, agn_driving_variability_model=agn_driving_variability_model, agn_driving_kwargs_variability=agn_driving_kwargs_variability, + source_type=source_type, + light_profile=light_profile, ) self.cosmo = cosmo - self._source_type = source_type + self._source_type = self.source.source_type self._lens_equation_solver = lens_equation_solver self._magnification_limit = magnification_limit self.kwargs_variab = kwargs_variability diff --git a/slsim/lensed_system_base.py b/slsim/lensed_system_base.py index 8c863d329..c4f79f296 100644 --- a/slsim/lensed_system_base.py +++ b/slsim/lensed_system_base.py @@ -24,6 +24,8 @@ def __init__( sn_modeldir=None, agn_driving_variability_model=None, agn_driving_kwargs_variability=None, + source_type="extended", + light_profile="single_sersic" ): """ :param source_dict: source properties @@ -65,6 +67,12 @@ def __init__( explanation of these parameters, see generate_signal() function in astro_util.py. :type agn_driving_kwargs_variability: dict + :param source_type: type of the source 'extended' or 'point_source' or + 'point_plus_extended' supported + :type source_type: str + :param light_profile: keyword for number of sersic profile to use in source + light model + :type light_profile: str . Either "single_sersic" or "double_sersic" . """ self.source = Source( source_dict=source_dict, @@ -78,6 +86,8 @@ def __init__( sn_modeldir=sn_modeldir, agn_driving_variability_model=agn_driving_variability_model, agn_driving_kwargs_variability=agn_driving_kwargs_variability, + source_type=source_type, + light_profile=light_profile ) self.deflector = Deflector( deflector_type=deflector_type, From 6badfd1aa46d8da74002be0febdc17cec56aff5c Mon Sep 17 00:00:00 2001 From: narayan Date: Thu, 10 Oct 2024 11:49:38 -0400 Subject: [PATCH 10/87] Change in lens class with recent input change. --- slsim/lens.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/slsim/lens.py b/slsim/lens.py index fee130fb2..ca2d5bcc2 100644 --- a/slsim/lens.py +++ b/slsim/lens.py @@ -130,8 +130,6 @@ def __init__( self._lens_equation_solver = lens_equation_solver self._magnification_limit = magnification_limit self.kwargs_variab = kwargs_variability - self.light_profile = light_profile - if self._source_type == "extended" and self.kwargs_variab is not None: warning_msg = ( "Extended source can not have variability. Therefore," @@ -672,7 +670,7 @@ def source_light_model_lenstronomy(self, band=None): self._source_type == "extended" or self._source_type == "point_plus_extended" ): - if self.light_profile == "single_sersic": + if self.source.light_profile == "single_sersic": source_models["source_light_model_list"] = ["SERSIC_ELLIPSE"] else: source_models["source_light_model_list"] = [ From b890b6fb9fc82aa4e4b59db048372291a16b5752 Mon Sep 17 00:00:00 2001 From: narayan Date: Thu, 10 Oct 2024 12:02:52 -0400 Subject: [PATCH 11/87] Change in lens class with recent input change. --- slsim/lens.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/slsim/lens.py b/slsim/lens.py index ca2d5bcc2..2494bc2ba 100644 --- a/slsim/lens.py +++ b/slsim/lens.py @@ -130,6 +130,7 @@ def __init__( self._lens_equation_solver = lens_equation_solver self._magnification_limit = magnification_limit self.kwargs_variab = kwargs_variability + if self._source_type == "extended" and self.kwargs_variab is not None: warning_msg = ( "Extended source can not have variability. Therefore," @@ -680,8 +681,7 @@ def source_light_model_lenstronomy(self, band=None): kwargs_source = self.source.kwargs_extended_source_light( draw_area=self.test_area, center_lens=self.deflector_position, - band=band, - light_profile_str=self.light_profile, + band=band ) else: # source_models['source_light_model_list'] = None From 9f4ab9217501a0060a7f3e47279c859938e55b26 Mon Sep 17 00:00:00 2001 From: narayan Date: Thu, 10 Oct 2024 12:05:10 -0400 Subject: [PATCH 12/87] Change in lens class with recent input change. --- slsim/lens.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/slsim/lens.py b/slsim/lens.py index 2494bc2ba..326f22d98 100644 --- a/slsim/lens.py +++ b/slsim/lens.py @@ -129,9 +129,9 @@ def __init__( self._source_type = self.source.source_type self._lens_equation_solver = lens_equation_solver self._magnification_limit = magnification_limit - self.kwargs_variab = kwargs_variability + self._kwargs_variab = self.source.kwargs_variability - if self._source_type == "extended" and self.kwargs_variab is not None: + if self._source_type == "extended" and self._kwargs_variab is not None: warning_msg = ( "Extended source can not have variability. Therefore," "variability information provided by you will not be used." From 074a607af9214cdbaab183c02b48c79df8f354f1 Mon Sep 17 00:00:00 2001 From: narayan Date: Thu, 10 Oct 2024 13:15:18 -0400 Subject: [PATCH 13/87] added a test area in LensPop's random draw function. --- slsim/false_positive.py | 14 +++++--------- slsim/false_positive_pop.py | 6 +++--- slsim/lens_pop.py | 2 ++ 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/slsim/false_positive.py b/slsim/false_positive.py index 509b5920b..2187d3129 100644 --- a/slsim/false_positive.py +++ b/slsim/false_positive.py @@ -19,9 +19,7 @@ def __init__( source_class, deflector_class, cosmo, - source_type="extended", test_area=4 * np.pi, - light_profile="single_sersic", los_config=None, los_dict=None, ): @@ -36,10 +34,11 @@ def __init__( 'point_plus_extended' supported :type source_type: str """ + self.deflector = deflector_class + self.source = source_class self.test_area = test_area self.cosmo = cosmo - self._source_type = source_type - self.light_profile = light_profile + self._source_type = self.source.source_type """self.source = Source( source_dict=source_dict, cosmo=cosmo @@ -48,8 +47,6 @@ def __init__( deflector_type=deflector_type, deflector_dict=deflector_dict, )""" - self.deflector = deflector_class - self.source = source_class self._lens_cosmo = LensCosmo( z_lens=float(self.deflector.redshift), @@ -297,7 +294,7 @@ def source_light_model_lenstronomy(self, band=None): self._source_type == "extended" or self._source_type == "point_plus_extended" ): - if self.light_profile == "single_sersic": + if self.source.light_profile == "single_sersic": source_models["source_light_model_list"] = ["SERSIC_ELLIPSE"] else: source_models["source_light_model_list"] = [ @@ -307,8 +304,7 @@ def source_light_model_lenstronomy(self, band=None): kwargs_source = self.source.kwargs_extended_source_light( draw_area=self.test_area, center_lens=self.deflector_position, - band=band, - light_profile_str=self.light_profile, + band=band ) else: # source_models['source_light_model_list'] = None diff --git a/slsim/false_positive_pop.py b/slsim/false_positive_pop.py index a9aa04ab5..b4fab2127 100644 --- a/slsim/false_positive_pop.py +++ b/slsim/false_positive_pop.py @@ -59,7 +59,9 @@ def draw_false_positive(self, number=1): source = self._sources.draw_source(z_max=z_max) _source = Source( source_dict=source, - cosmo=self.cosmo) + cosmo=self.cosmo, + source_type=self._sources.source_type, + light_profile=self._sources.light_profile) # Compute test area for false positive position. # This area will be used to determine the position of false positive. test_area = 3 * draw_test_area(deflector=lens) @@ -69,8 +71,6 @@ def draw_false_positive(self, number=1): deflector_class=_lens, source_class=_source, cosmo=self.cosmo, - source_type=self._sources.source_type, - light_profile=self._sources.light_profile, test_area=test_area ) diff --git a/slsim/lens_pop.py b/slsim/lens_pop.py index c104b990a..d0e55818b 100644 --- a/slsim/lens_pop.py +++ b/slsim/lens_pop.py @@ -84,6 +84,7 @@ def select_lens_at_random(self, **kwargs_lens_cut): while True: source = self._sources.draw_source() lens = self._lens_galaxies.draw_deflector() + test_area = draw_test_area(deflector=lens) gg_lens = Lens( deflector_dict=lens, source_dict=source, @@ -94,6 +95,7 @@ def select_lens_at_random(self, **kwargs_lens_cut): sn_absolute_mag_band=self.sn_absolute_mag_band, sn_absolute_zpsys=self.sn_absolute_zpsys, cosmo=self.cosmo, + test_area=test_area, source_type=self._sources.source_type, light_profile=self._sources.light_profile, lightcurve_time=self.lightcurve_time, From 826e670fa6dc5e6aa3aae671466dbe3320f1f7e4 Mon Sep 17 00:00:00 2001 From: narayan Date: Thu, 10 Oct 2024 17:32:48 -0400 Subject: [PATCH 14/87] added multiple source in FalsePositive and FalsePositivePop class. --- slsim/false_positive.py | 136 +++++++++++++++--------------------- slsim/false_positive_pop.py | 26 +++++-- slsim/lens.py | 2 + 3 files changed, 82 insertions(+), 82 deletions(-) diff --git a/slsim/false_positive.py b/slsim/false_positive.py index 2187d3129..553c72b5f 100644 --- a/slsim/false_positive.py +++ b/slsim/false_positive.py @@ -4,12 +4,6 @@ from lenstronomy.LensModel.lens_model import LensModel from slsim.ParamDistributions.los_config import LOSConfig from slsim.Util.param_util import ellipticity_slsim_to_lenstronomy -from lenstronomy.Util import constants - -#from slsim.lensed_system_base import LensedSystemBase -#from slsim.Sources.source import Source -#from slsim.Deflectors.deflector import Deflector - class FalsePositive(object): """Class to manage individual false positive.""" @@ -24,33 +18,36 @@ def __init__( los_dict=None, ): """ - - :param source_class: source class instance + :param source_class: A Source class instance or list of Source class instance :type source_class: Source class instance from slsim.Sources.source - :param deflector_dict: deflector instance - :type deflector_dict: Deflector class instance from slsim.Deflectors.deflector + :param deflector_class: deflector instance + :type deflector_class: Deflector class instance from slsim.Deflectors.deflector :param cosmo: astropy.cosmology instance - :param source_type: type of the source 'extended' or 'point_source' or - 'point_plus_extended' supported - :type source_type: str + :param test_area: area of disk around one lensing galaxies to be investigated + on (in arc-seconds^2). + :param los_config: LOSConfig instance which manages line-of-sight (LOS) effects + and Gaussian mixture models in a simulation or analysis context. + :param los_dict: line of sight dictionary (optional, takes these values instead of drawing from distribution) + Takes "gamma" = [gamma1, gamma2] and "kappa" = kappa as entries + :type los_dict: dict """ self.deflector = deflector_class self.source = source_class self.test_area = test_area self.cosmo = cosmo - self._source_type = self.source.source_type - """self.source = Source( - source_dict=source_dict, - cosmo=cosmo - )""" - """self.deflector = Deflector( - deflector_type=deflector_type, - deflector_dict=deflector_dict, - )""" - + if isinstance(self.source, list): + source_z=self.source[0].redshift + self._source_type = self.source[0].source_type + self.source_number = len(self.source) + self.single_source_class = self.source[0] # to access some common kwargs. + else: + source_z=self.source.redshift + self._source_type = self.source.source_type + self.source_number = 1 + self.single_source_class = self.source self._lens_cosmo = LensCosmo( z_lens=float(self.deflector.redshift), - z_source=float(self.source.redshift), + z_source=float(source_z), cosmo=self.cosmo, ) @@ -82,10 +79,16 @@ def deflector_redshift(self): def source_redshift(self): """ - :return: source redshift + :return: a source redshift or list of source redshift """ - return self.source.redshift - + if self.source_number==1: + source_redshift = self.source.redshift + else: + source_redshift=[] + for i in range(self.source_number): + source_redshift.append(self.source[i].redshift) + return source_redshift + @property def external_convergence(self): """ @@ -104,30 +107,6 @@ def external_shear(self): gamma1, gamma2, _ = self.los_linear_distortions return (gamma1**2 + gamma2**2) ** 0.5 - @property - def einstein_radius_deflector(self): - """Einstein radius, from SIS approximation (coming from velocity dispersion) - without line-of-sight correction. - - :return: - """ - if not hasattr(self, "_theta_E"): - if self.deflector.redshift >= self.source.redshift: - self._theta_E = 0 - elif self.deflector.deflector_type in ["EPL"]: - self._theta_E = self._lens_cosmo.sis_sigma_v2theta_E( - float(self.deflector.velocity_dispersion(cosmo=self.cosmo)) - ) - else: - # numerical solution for the Einstein radius - lens_model_list, kwargs_lens = self.deflector_mass_model_lenstronomy() - lens_model = LensModel(lens_model_list=lens_model_list) - lens_analysis = LensProfileAnalysis(lens_model=lens_model) - self._theta_E = lens_analysis.effective_einstein_radius( - kwargs_lens, r_min=1e-3, r_max=5e1, num_points=50 - ) - return self._theta_E - @property def einstein_radius(self): """Einstein radius, from SIS approximation (coming from velocity dispersion) + @@ -175,8 +154,12 @@ def _calculate_los_linear_distortions(self): :return: kappa, gamma1, gamma2 """ + if self.source_number==1: + source_z = self.source_redshift + else: + source_z = self.source_redshift[0] return self.los_config.calculate_los_linear_distortions( - source_redshift=self.source_redshift, + source_redshift=source_z, deflector_redshift=self.deflector_redshift, ) @@ -294,18 +277,33 @@ def source_light_model_lenstronomy(self, band=None): self._source_type == "extended" or self._source_type == "point_plus_extended" ): - if self.source.light_profile == "single_sersic": - source_models["source_light_model_list"] = ["SERSIC_ELLIPSE"] + if self.source_number==1: + source_class=self.source + else: + source_class=self.source[0] + if source_class.light_profile == "single_sersic": + source_models["source_light_model_list"] = [ + "SERSIC_ELLIPSE"]*self.source_number + #In this case we will consider a single source with double sersic profile. else: source_models["source_light_model_list"] = [ "SERSIC_ELLIPSE", "SERSIC_ELLIPSE", ] - kwargs_source = self.source.kwargs_extended_source_light( - draw_area=self.test_area, - center_lens=self.deflector_position, - band=band - ) + if self.source_number==1: + kwargs_source = self.source.kwargs_extended_source_light( + draw_area=self.test_area, + center_lens=self.deflector_position, + band=band + ) + else: + kwargs_source=[] + for i in range(self.source_number): + kwargs_source.append(self.source[i].kwargs_extended_source_light( + draw_area=self.test_area, + center_lens=self.deflector_position, + band=band + )[0]) else: # source_models['source_light_model_list'] = None kwargs_source = None @@ -328,22 +326,4 @@ def source_light_model_lenstronomy(self, band=None): kwargs_ps = None all_source_kwarg_dict["kwargs_source"] = kwargs_source all_source_kwarg_dict["kwargs_ps"] = kwargs_ps - return source_models, all_source_kwarg_dict - -def theta_e_when_source_infinity(deflector_dict=None, v_sigma=None): - """Calculate Einstein radius in arc-seconds for a source at infinity. - - :param deflector_dict: deflector properties - :param v_sigma: velocity dispersion in km/s - :return: Einstein radius in arc-seconds - """ - if v_sigma is None: - if deflector_dict is None: - raise ValueError("Either deflector_dict or v_sigma must be provided") - else: - v_sigma = deflector_dict["vel_disp"] - - theta_E_infinity = ( - 4 * np.pi * (v_sigma * 1000.0 / constants.c) ** 2 / constants.arcsec - ) - return theta_E_infinity + return source_models, all_source_kwarg_dict \ No newline at end of file diff --git a/slsim/false_positive_pop.py b/slsim/false_positive_pop.py index b4fab2127..ba011fb40 100644 --- a/slsim/false_positive_pop.py +++ b/slsim/false_positive_pop.py @@ -7,6 +7,7 @@ from slsim.Sources.source import Source from slsim.Deflectors.deflector import Deflector from slsim.lens_pop import draw_test_area +import random class FalsePositivePop(object): @@ -18,6 +19,8 @@ def __init__( blue_galaxy_population: SourcePopBase, cosmo: Optional[Cosmology] = None, los_config: Optional[LOSConfig] = None, + source_number_choice: Optional[list[int]] = [1, 2, 3], + weights_for_source_number: Optional[list[int]] = None ): """ Args: @@ -26,11 +29,17 @@ def __init__( cosmo (Optional[Cosmology], optional): AstroPy Cosmology instance. If None, defaults to flat LCDM with h0=0.7 and Om0=0.3. Defaults to None. los_config (Optional[LOSConfig], optional): Configuration for line of sight distribution. Defaults to None. + source_number_choice (Optional[list[int]], optional): A list of integers to choose source number from. If None, defaults to [1, 2, 3]. + weights (Optional[list[int]], optional): A list of weights corresponding to the probabilities of selecting each value + in source_number_choice. If None, all choices are equally likely. + Defaults to None. """ self.cosmo = cosmo self._lens_galaxies = elliptical_galaxy_population self._sources = blue_galaxy_population + self._choice = source_number_choice + self._weights = weights_for_source_number self.los_config = los_config if self.los_config is None: self.los_config = LOSConfig() @@ -55,13 +64,22 @@ def draw_false_positive(self, number=1): deflector_dict=lens) tolerance = 0.002 z_max = _lens.redshift + tolerance - # Try to draw a source with the z_max based on the lens redshift - source = self._sources.draw_source(z_max=z_max) - _source = Source( + # Try to draw a source with the z_max based on the lens redshift and + # source number choice. + source_number = random.choices( + self._choice, weights=self._weights)[0] + source_list=[] + for _ in range(source_number): + source = self._sources.draw_source(z_max=z_max) + source_list.append(Source( source_dict=source, cosmo=self.cosmo, source_type=self._sources.source_type, - light_profile=self._sources.light_profile) + light_profile=self._sources.light_profile)) + if source_number==1: + _source=source_list[0] + else: + _source=source_list # Compute test area for false positive position. # This area will be used to determine the position of false positive. test_area = 3 * draw_test_area(deflector=lens) diff --git a/slsim/lens.py b/slsim/lens.py index 326f22d98..e69973216 100644 --- a/slsim/lens.py +++ b/slsim/lens.py @@ -84,6 +84,8 @@ def __init__( :type light_profile: str . Either "single_sersic" or "double_sersic" . :param lightcurve_time: observation time array for lightcurve in unit of days. :type lightcurve_time: array + :param los_config: LOSConfig instance which manages line-of-sight (LOS) effects + and Gaussian mixture models in a simulation or analysis context. :param sn_modeldir: sn_modeldir is the path to the directory containing files needed to initialize the sncosmo.model class. For example, sn_modeldir = 'C:/Users/username/Documents/SALT3.NIR_WAVEEXT'. These data can From af772406153ded1845d69d776fb6ab943b1a462e Mon Sep 17 00:00:00 2001 From: narayan Date: Thu, 10 Oct 2024 17:49:26 -0400 Subject: [PATCH 15/87] minor change --- slsim/false_positive.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/slsim/false_positive.py b/slsim/false_positive.py index 553c72b5f..c344ac6b7 100644 --- a/slsim/false_positive.py +++ b/slsim/false_positive.py @@ -1,7 +1,5 @@ import numpy as np -from lenstronomy.Analysis.lens_profile import LensProfileAnalysis from lenstronomy.Cosmo.lens_cosmo import LensCosmo -from lenstronomy.LensModel.lens_model import LensModel from slsim.ParamDistributions.los_config import LOSConfig from slsim.Util.param_util import ellipticity_slsim_to_lenstronomy @@ -27,8 +25,9 @@ def __init__( on (in arc-seconds^2). :param los_config: LOSConfig instance which manages line-of-sight (LOS) effects and Gaussian mixture models in a simulation or analysis context. - :param los_dict: line of sight dictionary (optional, takes these values instead of drawing from distribution) - Takes "gamma" = [gamma1, gamma2] and "kappa" = kappa as entries + :param los_dict: line of sight dictionary (optional, takes these values instead + of drawing from distribution) Takes "gamma" = [gamma1, gamma2] and + "kappa" = kappa as entries :type los_dict: dict """ self.deflector = deflector_class From 742bbd1087e0b4ef9c2ac2f5e90869c8270df538 Mon Sep 17 00:00:00 2001 From: narayan Date: Thu, 10 Oct 2024 19:50:33 -0400 Subject: [PATCH 16/87] added test functions for the false positive. --- slsim/false_positive.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/slsim/false_positive.py b/slsim/false_positive.py index c344ac6b7..90d04054f 100644 --- a/slsim/false_positive.py +++ b/slsim/false_positive.py @@ -2,6 +2,7 @@ from lenstronomy.Cosmo.lens_cosmo import LensCosmo from slsim.ParamDistributions.los_config import LOSConfig from slsim.Util.param_util import ellipticity_slsim_to_lenstronomy +from slsim.lens import theta_e_when_source_infinity class FalsePositive(object): """Class to manage individual false positive.""" @@ -113,7 +114,8 @@ def einstein_radius(self): :return: Einstein radius [arc seconds] """ - theta_E = self.einstein_radius_deflector + theta_E = theta_e_when_source_infinity( + v_sigma=self.deflector_velocity_dispersion()) _, _, kappa_ext = self.los_linear_distortions return theta_E / (1 - kappa_ext) From 41e62b9fcd6dbeab846ea9038281491748556a23 Mon Sep 17 00:00:00 2001 From: narayan Date: Thu, 10 Oct 2024 19:51:05 -0400 Subject: [PATCH 17/87] added test functions for the false positive. --- tests/test_false_positive.py | 97 ++++++++++++++++++++++++++++++++ tests/test_false_positive_pop.py | 61 ++++++++++++++++++++ 2 files changed, 158 insertions(+) create mode 100644 tests/test_false_positive.py create mode 100644 tests/test_false_positive_pop.py diff --git a/tests/test_false_positive.py b/tests/test_false_positive.py new file mode 100644 index 000000000..ba1d2516e --- /dev/null +++ b/tests/test_false_positive.py @@ -0,0 +1,97 @@ +import pytest +import numpy as np +from astropy.cosmology import FlatLambdaCDM +from slsim.ParamDistributions.los_config import LOSConfig +from slsim.Sources.source import Source +from slsim.Deflectors.deflector import Deflector +import slsim.Sources as sources +import slsim.Deflectors as deflectors +import slsim.Pipelines as pipelines +from slsim.false_positive import FalsePositive +from astropy.units import Quantity + +sky_area = Quantity(value=0.01, unit="deg2") +galaxy_simulation_pipeline = pipelines.SkyPyPipeline( + skypy_config=None, + sky_area=sky_area, + filters=None, +) +kwargs_deflector_cut = {"band": "g", "band_max": 28, "z_min": 0.01, "z_max": 2.5} +kwargs_source_cut = {"band": "g", "band_max": 28, "z_min": 0.1, "z_max": 5.0} +def test_false_positive(): + # Mock objects for source_class and deflector_class + + + # Initialize a cosmology instance + cosmo = FlatLambdaCDM(H0=70, Om0=0.3) + lens_galaxies = deflectors.EllipticalLensGalaxies( + galaxy_list=galaxy_simulation_pipeline.red_galaxies, + kwargs_cut=kwargs_deflector_cut, + kwargs_mass2light=0.1, + cosmo=cosmo, + sky_area=sky_area,) + source_galaxies=sources.Galaxies( + galaxy_list=galaxy_simulation_pipeline.blue_galaxies, + kwargs_cut=kwargs_source_cut, + cosmo=cosmo, + sky_area=sky_area, + catalog_type="skypy", + ) + single_deflector=lens_galaxies.draw_deflector() + single_source1=source_galaxies.draw_source() + single_source2=source_galaxies.draw_source() + lens = Deflector(deflector_type="EPL", + deflector_dict=single_deflector) + source=Source(source_dict=single_source1, + cosmo=cosmo, + source_type="extended", + light_profile="single_sersic") + source_list=[Source(source_dict=single_source1, + cosmo=cosmo, + source_type="extended", + light_profile="single_sersic"), Source(source_dict=single_source2, + cosmo=cosmo, + source_type="extended", + light_profile="single_sersic")] + # LOS configuration + los_config = LOSConfig() + + # Create an instance of FalsePositive + false_positive_instance_1 = FalsePositive( + source_class=source, + deflector_class=lens, + cosmo=cosmo, + test_area=4 * np.pi, + ) + false_positive_instance_2 = FalsePositive( + source_class=source_list, + deflector_class=lens, + cosmo=cosmo, + test_area=4 * np.pi, + los_config=los_config, + ) + assert false_positive_instance_1.source_number == 1 + assert false_positive_instance_2.source_number == 2 + assert false_positive_instance_1.lenstronomy_kwargs("i")[0][ + "lens_light_model_list"][0] == 'SERSIC_ELLIPSE' + assert len(false_positive_instance_2.lenstronomy_kwargs("i")[0][ + "lens_light_model_list"]) == 3 + assert len(false_positive_instance_2.lenstronomy_kwargs("i")[1][ + "kwargs_lens_light"]) == 3 + assert len(false_positive_instance_2.deflector_position) == 2 + assert false_positive_instance_2.deflector_redshift == single_deflector["z"] + assert false_positive_instance_1.source_redshift == single_source1["z"] + assert np.all(false_positive_instance_2.source_redshift) == np.all( + np.array([single_source1["z"], single_source2["z"]])) + assert false_positive_instance_1.external_convergence < 0.2 + assert false_positive_instance_1.einstein_radius < 2.5 + assert false_positive_instance_1.deflector_magnitude(band="i") ==\ + single_deflector["mag_i"] + assert false_positive_instance_1.extended_source_magnitude(band="i") ==\ + single_source1["mag_i"] + assert len(false_positive_instance_1.deflector_ellipticity()) == 4 + assert false_positive_instance_1.deflector_stellar_mass() ==\ + single_deflector["stellar_mass"] + +if __name__ == "__main__": + pytest.main() \ No newline at end of file diff --git a/tests/test_false_positive_pop.py b/tests/test_false_positive_pop.py new file mode 100644 index 000000000..6c16b92b7 --- /dev/null +++ b/tests/test_false_positive_pop.py @@ -0,0 +1,61 @@ +import pytest +from astropy.cosmology import FlatLambdaCDM +import slsim.Sources as sources +import slsim.Deflectors as deflectors +import slsim.Pipelines as pipelines +from slsim.false_positive import FalsePositive +from slsim.false_positive_pop import FalsePositivePop +from astropy.units import Quantity + +sky_area = Quantity(value=0.01, unit="deg2") +cosmo = FlatLambdaCDM(H0=70, Om0=0.3) +galaxy_simulation_pipeline = pipelines.SkyPyPipeline( + skypy_config=None, + sky_area=sky_area, + filters=None, +) +kwargs_deflector_cut = {"band": "g", "band_max": 28, "z_min": 0.01, "z_max": 2.5} +kwargs_source_cut = {"band": "g", "band_max": 28, "z_min": 0.1, "z_max": 5.0} +red_galaxy_list = galaxy_simulation_pipeline.red_galaxies +blue_galaxy_list = galaxy_simulation_pipeline.blue_galaxies +lens_galaxies = deflectors.EllipticalLensGalaxies( + galaxy_list=red_galaxy_list, + kwargs_cut=kwargs_deflector_cut, + kwargs_mass2light=0.1, + cosmo=cosmo, + sky_area=sky_area,) +source_galaxies=sources.Galaxies( + galaxy_list=blue_galaxy_list, + kwargs_cut=kwargs_source_cut, + cosmo=cosmo, + sky_area=sky_area, + catalog_type="skypy", +) +@pytest.fixture +def false_positive_pop_instance1(): + fp_pop = FalsePositivePop( + elliptical_galaxy_population=lens_galaxies, + blue_galaxy_population=source_galaxies, + cosmo=cosmo, + source_number_choice=[1]) + return fp_pop + +@pytest.fixture +def false_positive_pop_instance2(): + fp_pop = FalsePositivePop( + elliptical_galaxy_population=lens_galaxies, + blue_galaxy_population=source_galaxies, + cosmo=cosmo, + source_number_choice=[2]) + return fp_pop + +def test_draw_false_positive_single_source(false_positive_pop_instance1): + draw_fp = false_positive_pop_instance1.draw_false_positive() + assert isinstance(draw_fp, object) + +def test_draw_false_positive_single_source(false_positive_pop_instance2): + draw_fp = false_positive_pop_instance2.draw_false_positive(number=2) + assert isinstance(draw_fp, list) + +if __name__ == "__main__": + pytest.main() From 224ac6f575115771d30fd51dcf255eb8108816ec Mon Sep 17 00:00:00 2001 From: narayan Date: Thu, 10 Oct 2024 20:04:17 -0400 Subject: [PATCH 18/87] corrected some test functions with added features. --- tests/test_Source/test_source.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/test_Source/test_source.py b/tests/test_Source/test_source.py index 9e50196f1..2d30a42ad 100644 --- a/tests/test_Source/test_source.py +++ b/tests/test_Source/test_source.py @@ -189,6 +189,7 @@ def setup_method(self): sn_type="Ia", lightcurve_time=np.linspace(-20, 50, 100), cosmo=cosmo, + light_profile="double_sersic" ) self.source4 = Source( self.source_dict3, @@ -209,6 +210,7 @@ def setup_method(self): sn_type="Ia", lightcurve_time=np.linspace(-20, 50, 100), cosmo=cosmo, + light_profile="double_sersic" ) self.source6 = Source( @@ -220,6 +222,7 @@ def setup_method(self): sn_type="Ia", lightcurve_time=np.linspace(-20, 50, 100), cosmo=cosmo, + light_profile="triplet" ) self.source7 = Source( @@ -619,17 +622,17 @@ def test_kwargs_extended_source_light_double_sersic(self): center_lens = np.array([0, 0]) draw_area = 4 * np.pi kwargs = self.source3.kwargs_extended_source_light( - center_lens, draw_area, band="i", light_profile_str="double_sersic" + center_lens, draw_area, band="i" ) assert len(kwargs) == 2 assert all(isinstance(kwargs_item, dict) for kwargs_item in kwargs) with pytest.raises(ValueError): - self.source5.kwargs_extended_source_light( - center_lens, draw_area, band="i", light_profile_str="triple" + self.source6.kwargs_extended_source_light( + center_lens, draw_area, band="i" ) with pytest.raises(ValueError): self.source5.kwargs_extended_source_light( - center_lens, draw_area, band="i", light_profile_str="double_sersic" + center_lens, draw_area, band="i" ) with pytest.raises(ValueError): self.source10.kwargs_variability_extracted From aac4d0c575abd6b28a16f03d0215acdb5d7b0f3e Mon Sep 17 00:00:00 2001 From: narayan Date: Thu, 10 Oct 2024 20:06:40 -0400 Subject: [PATCH 19/87] corrected some test functions with added features. --- tests/test_Source/test_galaxies.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_Source/test_galaxies.py b/tests/test_Source/test_galaxies.py index ee7229cb6..ea9ce8420 100644 --- a/tests/test_Source/test_galaxies.py +++ b/tests/test_Source/test_galaxies.py @@ -283,8 +283,10 @@ def test_source_number(self): def test_draw_source(self): galaxy = self.galaxies.draw_source() galaxy_1 = self.galaxies4.draw_source() + galaxy_2 = self.galaxies.draw_source(z_max=1) assert len(galaxy) > 0 assert galaxy_1["n_sersic"] == 1 + assert galaxy_2["z"] < 1+0.002 with pytest.raises(ValueError): self.galaxies5.draw_source() From da3def4127c0898b3e1a0f95e61471bb22dc68d6 Mon Sep 17 00:00:00 2001 From: narayan Date: Thu, 10 Oct 2024 20:38:30 -0400 Subject: [PATCH 20/87] corrected some test functions with added features. --- slsim/lens_pop.py | 10 ++++++++-- tests/test_lens_pop.py | 3 ++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/slsim/lens_pop.py b/slsim/lens_pop.py index d0e55818b..6c3c47c7d 100644 --- a/slsim/lens_pop.py +++ b/slsim/lens_pop.py @@ -71,20 +71,26 @@ def __init__( if self.los_config is None: self.los_config = LOSConfig() - def select_lens_at_random(self, **kwargs_lens_cut): + def select_lens_at_random(self, test_area=None, **kwargs_lens_cut): """Draw a random lens within the cuts of the lens and source, with possible additional cut in the lensing configuration. # TODO: make sure mass function is preserved, # as well as option to draw all lenses within the cuts within the area + :param test_area: area of disk around one lensing galaxies to be investigated + on (in arc-seconds^2). If None, computed using deflector's velocity + dispersion. :return: Lens() instance with parameters of the deflector and lens and source light """ while True: source = self._sources.draw_source() lens = self._lens_galaxies.draw_deflector() - test_area = draw_test_area(deflector=lens) + if test_area is None: + test_area = draw_test_area(deflector=lens) + else: + test_area=test_area gg_lens = Lens( deflector_dict=lens, source_dict=source, diff --git a/tests/test_lens_pop.py b/tests/test_lens_pop.py index 946134b62..59e9ff481 100644 --- a/tests/test_lens_pop.py +++ b/tests/test_lens_pop.py @@ -183,7 +183,8 @@ def test_cluster_lens_pop_instance(): ) kwargs_lens_cut = {} - pes_lens_class = cluster_lens_pop.select_lens_at_random(**kwargs_lens_cut) + pes_lens_class = cluster_lens_pop.select_lens_at_random(test_area=4*np.pi, + **kwargs_lens_cut) assert pes_lens_class.deflector.deflector_type == "NFW_CLUSTER" kwargs_model, kwargs_params = pes_lens_class.lenstronomy_kwargs(band="g") assert len(kwargs_model["lens_model_list"]) >= 3 # halo, 1>= subhalo, LoS From 85d89993bba89ace20f5f7670ccc028e5b653056 Mon Sep 17 00:00:00 2001 From: narayan Date: Fri, 11 Oct 2024 11:23:16 -0400 Subject: [PATCH 21/87] improved false positive test. --- slsim/false_positive.py | 75 ++++++++++++++---------------------- tests/test_false_positive.py | 21 +++++++++- 2 files changed, 48 insertions(+), 48 deletions(-) diff --git a/slsim/false_positive.py b/slsim/false_positive.py index 90d04054f..88254fd1f 100644 --- a/slsim/false_positive.py +++ b/slsim/false_positive.py @@ -274,57 +274,38 @@ def source_light_model_lenstronomy(self, band=None): """ source_models = {} all_source_kwarg_dict = {} - if ( + """if ( self._source_type == "extended" or self._source_type == "point_plus_extended" - ): - if self.source_number==1: - source_class=self.source - else: - source_class=self.source[0] - if source_class.light_profile == "single_sersic": - source_models["source_light_model_list"] = [ - "SERSIC_ELLIPSE"]*self.source_number - #In this case we will consider a single source with double sersic profile. - else: - source_models["source_light_model_list"] = [ - "SERSIC_ELLIPSE", - "SERSIC_ELLIPSE", - ] - if self.source_number==1: - kwargs_source = self.source.kwargs_extended_source_light( - draw_area=self.test_area, - center_lens=self.deflector_position, - band=band - ) - else: - kwargs_source=[] - for i in range(self.source_number): - kwargs_source.append(self.source[i].kwargs_extended_source_light( - draw_area=self.test_area, - center_lens=self.deflector_position, - band=band - )[0]) + ):""" + if self.source_number==1: + source_class=self.source else: - # source_models['source_light_model_list'] = None - kwargs_source = None - - if ( - self._source_type == "point_source" - or self._source_type == "point_plus_extended" - ): - source_models["point_source_model_list"] = ["LENSED_POSITION"] - img_x, img_y = self.point_source_image_positions() - if band is None: - image_magnitudes = np.abs(self.point_source_magnification()) - else: - image_magnitudes = self.point_source_magnitude(band=band, lensed=False) - kwargs_ps = [ - {"ra_image": img_x, "dec_image": img_y, "magnitude": image_magnitudes} - ] + source_class=self.source[0] + if source_class.light_profile == "single_sersic": + source_models["source_light_model_list"] = [ + "SERSIC_ELLIPSE"]*self.source_number + #In this case we will consider a single source with double sersic profile. else: - # source_models['point_source_model'] = None - kwargs_ps = None + raise ValueError("Provided light profile is not supported. Supported" + " light profile is single_sersic") + if self.source_number==1: + kwargs_source = self.source.kwargs_extended_source_light( + draw_area=self.test_area, + center_lens=self.deflector_position, + band=band + ) + else: + kwargs_source=[] + for i in range(self.source_number): + kwargs_source.append(self.source[i].kwargs_extended_source_light( + draw_area=self.test_area, + center_lens=self.deflector_position, + band=band + )[0]) + + + kwargs_ps = None all_source_kwarg_dict["kwargs_source"] = kwargs_source all_source_kwarg_dict["kwargs_ps"] = kwargs_ps return source_models, all_source_kwarg_dict \ No newline at end of file diff --git a/tests/test_false_positive.py b/tests/test_false_positive.py index ba1d2516e..5a6e7cc33 100644 --- a/tests/test_false_positive.py +++ b/tests/test_false_positive.py @@ -46,6 +46,10 @@ def test_false_positive(): cosmo=cosmo, source_type="extended", light_profile="single_sersic") + source2=Source(source_dict=single_source1, + cosmo=cosmo, + source_type="extended", + light_profile="double_sersic") source_list=[Source(source_dict=single_source1, cosmo=cosmo, source_type="extended", @@ -70,6 +74,15 @@ def test_false_positive(): test_area=4 * np.pi, los_config=los_config, ) + false_positive_instance_3 = FalsePositive( + source_class=source2, + deflector_class=lens, + cosmo=cosmo, + test_area=4 * np.pi, + los_config=los_config, + ) + required_keys = {'magnitude', 'R_sersic', 'n_sersic', 'e1', 'e2', 'center_x', + 'center_y'} assert false_positive_instance_1.source_number == 1 assert false_positive_instance_2.source_number == 2 assert false_positive_instance_1.lenstronomy_kwargs("i")[0][ @@ -83,7 +96,8 @@ def test_false_positive(): assert false_positive_instance_1.source_redshift == single_source1["z"] assert np.all(false_positive_instance_2.source_redshift) == np.all( np.array([single_source1["z"], single_source2["z"]])) - assert false_positive_instance_1.external_convergence < 0.2 + assert false_positive_instance_1.external_convergence < 0.1 + assert false_positive_instance_1.external_shear < 0.2 assert false_positive_instance_1.einstein_radius < 2.5 assert false_positive_instance_1.deflector_magnitude(band="i") ==\ single_deflector["mag_i"] @@ -92,6 +106,11 @@ def test_false_positive(): assert len(false_positive_instance_1.deflector_ellipticity()) == 4 assert false_positive_instance_1.deflector_stellar_mass() ==\ single_deflector["stellar_mass"] + assert set(false_positive_instance_1.deflector_light_model_lenstronomy(band="i" + )[1][0].keys()) == required_keys + with pytest.raises(ValueError): + false_positive_instance_3.source_light_model_lenstronomy(band="i") + if __name__ == "__main__": pytest.main() \ No newline at end of file From 20cdf1e4f480e3625845b540c49b3d05fe0d62c9 Mon Sep 17 00:00:00 2001 From: narayan Date: Fri, 11 Oct 2024 11:29:17 -0400 Subject: [PATCH 22/87] improved false positive pop test. --- tests/test_false_positive_pop.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/tests/test_false_positive_pop.py b/tests/test_false_positive_pop.py index 6c16b92b7..ea2319000 100644 --- a/tests/test_false_positive_pop.py +++ b/tests/test_false_positive_pop.py @@ -3,7 +3,6 @@ import slsim.Sources as sources import slsim.Deflectors as deflectors import slsim.Pipelines as pipelines -from slsim.false_positive import FalsePositive from slsim.false_positive_pop import FalsePositivePop from astropy.units import Quantity @@ -39,7 +38,9 @@ def false_positive_pop_instance1(): cosmo=cosmo, source_number_choice=[1]) return fp_pop - +def test_draw_false_positive_single_source(false_positive_pop_instance1): + draw_fp = false_positive_pop_instance1.draw_false_positive() + assert isinstance(draw_fp, object) @pytest.fixture def false_positive_pop_instance2(): fp_pop = FalsePositivePop( @@ -48,11 +49,6 @@ def false_positive_pop_instance2(): cosmo=cosmo, source_number_choice=[2]) return fp_pop - -def test_draw_false_positive_single_source(false_positive_pop_instance1): - draw_fp = false_positive_pop_instance1.draw_false_positive() - assert isinstance(draw_fp, object) - def test_draw_false_positive_single_source(false_positive_pop_instance2): draw_fp = false_positive_pop_instance2.draw_false_positive(number=2) assert isinstance(draw_fp, list) From 55e58ee0dbbcd0e5b72a38e022725ed75dc13d2f Mon Sep 17 00:00:00 2001 From: narayan Date: Fri, 11 Oct 2024 11:55:37 -0400 Subject: [PATCH 23/87] improved false positive pop test. --- tests/test_false_positive_pop.py | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/tests/test_false_positive_pop.py b/tests/test_false_positive_pop.py index ea2319000..d7928e2e8 100644 --- a/tests/test_false_positive_pop.py +++ b/tests/test_false_positive_pop.py @@ -30,28 +30,23 @@ sky_area=sky_area, catalog_type="skypy", ) -@pytest.fixture -def false_positive_pop_instance1(): - fp_pop = FalsePositivePop( +def test_draw_false_positive_single(): + fp_pop1 = FalsePositivePop( elliptical_galaxy_population=lens_galaxies, blue_galaxy_population=source_galaxies, cosmo=cosmo, source_number_choice=[1]) - return fp_pop -def test_draw_false_positive_single_source(false_positive_pop_instance1): - draw_fp = false_positive_pop_instance1.draw_false_positive() - assert isinstance(draw_fp, object) -@pytest.fixture -def false_positive_pop_instance2(): - fp_pop = FalsePositivePop( + draw_fp1 = fp_pop1.draw_false_positive() + assert isinstance(draw_fp1, object) + +def test_draw_false_positive_multiple(): + fp_pop2 = FalsePositivePop( elliptical_galaxy_population=lens_galaxies, blue_galaxy_population=source_galaxies, cosmo=cosmo, source_number_choice=[2]) - return fp_pop -def test_draw_false_positive_single_source(false_positive_pop_instance2): - draw_fp = false_positive_pop_instance2.draw_false_positive(number=2) - assert isinstance(draw_fp, list) + draw_fp2 = fp_pop2.draw_false_positive(number=2) + assert isinstance(draw_fp2, list) if __name__ == "__main__": pytest.main() From a4b05a9db84596d8e7ba7803819ed8e79a1c76e5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 11 Oct 2024 15:58:26 +0000 Subject: [PATCH 24/87] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- slsim/Sources/galaxies.py | 9 +- slsim/Sources/source.py | 6 +- slsim/false_positive.py | 79 ++++++++-------- slsim/false_positive_pop.py | 43 +++++---- slsim/lens.py | 4 +- slsim/lens_pop.py | 7 +- slsim/lensed_system_base.py | 4 +- tests/test_Source/test_galaxies.py | 2 +- tests/test_Source/test_source.py | 14 +-- tests/test_false_positive.py | 142 +++++++++++++++++++---------- tests/test_false_positive_pop.py | 27 ++++-- tests/test_lens_pop.py | 5 +- 12 files changed, 195 insertions(+), 147 deletions(-) diff --git a/slsim/Sources/galaxies.py b/slsim/Sources/galaxies.py index a857baf96..fa2674a6e 100644 --- a/slsim/Sources/galaxies.py +++ b/slsim/Sources/galaxies.py @@ -121,16 +121,15 @@ def source_number_selected(self): return self._num_select def draw_source(self, z_max=None): - """Choose source at random. - :param z_max: maximum redshift for source to be drawn. + """Choose source at random. :param z_max: maximum redshift for source to be + drawn. :return: dictionary of source """ if z_max is not None: - filtered_galaxies = self._galaxy_select[ - self._galaxy_select['z'] < z_max] + filtered_galaxies = self._galaxy_select[self._galaxy_select["z"] < z_max] if len(filtered_galaxies) > 0: - index = random.randint(0, len(filtered_galaxies) -1) + index = random.randint(0, len(filtered_galaxies) - 1) galaxy = filtered_galaxies[index] else: raise ValueError(f"No galaxies found with z < {z_max}.") diff --git a/slsim/Sources/source.py b/slsim/Sources/source.py index ef42acd09..7767dc316 100644 --- a/slsim/Sources/source.py +++ b/slsim/Sources/source.py @@ -33,7 +33,7 @@ def __init__( agn_driving_variability_model=None, agn_driving_kwargs_variability=None, source_type="extended", - light_profile="single_sersic" + light_profile="single_sersic", ): """ :param source_dict: Source properties @@ -493,9 +493,7 @@ def point_source_position(self, center_lens, draw_area): return self._center_point_source return extended_source_center - def kwargs_extended_source_light( - self, center_lens, draw_area, band=None - ): + def kwargs_extended_source_light(self, center_lens, draw_area, band=None): """Provides dictionary of keywords for the source light model(s). Kewords used are in lenstronomy conventions. diff --git a/slsim/false_positive.py b/slsim/false_positive.py index 88254fd1f..39679e9b2 100644 --- a/slsim/false_positive.py +++ b/slsim/false_positive.py @@ -4,6 +4,7 @@ from slsim.Util.param_util import ellipticity_slsim_to_lenstronomy from slsim.lens import theta_e_when_source_infinity + class FalsePositive(object): """Class to manage individual false positive.""" @@ -26,8 +27,8 @@ def __init__( on (in arc-seconds^2). :param los_config: LOSConfig instance which manages line-of-sight (LOS) effects and Gaussian mixture models in a simulation or analysis context. - :param los_dict: line of sight dictionary (optional, takes these values instead - of drawing from distribution) Takes "gamma" = [gamma1, gamma2] and + :param los_dict: line of sight dictionary (optional, takes these values instead + of drawing from distribution) Takes "gamma" = [gamma1, gamma2] and "kappa" = kappa as entries :type los_dict: dict """ @@ -36,12 +37,12 @@ def __init__( self.test_area = test_area self.cosmo = cosmo if isinstance(self.source, list): - source_z=self.source[0].redshift + source_z = self.source[0].redshift self._source_type = self.source[0].source_type self.source_number = len(self.source) - self.single_source_class = self.source[0] # to access some common kwargs. + self.single_source_class = self.source[0] # to access some common kwargs. else: - source_z=self.source.redshift + source_z = self.source.redshift self._source_type = self.source.source_type self.source_number = 1 self.single_source_class = self.source @@ -58,7 +59,6 @@ def __init__( los_dict = {} self.los_config = LOSConfig(**los_dict) - @property def deflector_position(self): """Center of the deflector position. @@ -66,7 +66,7 @@ def deflector_position(self): :return: [x_pox, y_pos] in arc seconds """ return self.deflector.deflector_center - + @property def deflector_redshift(self): """ @@ -81,14 +81,14 @@ def source_redshift(self): :return: a source redshift or list of source redshift """ - if self.source_number==1: + if self.source_number == 1: source_redshift = self.source.redshift else: - source_redshift=[] + source_redshift = [] for i in range(self.source_number): source_redshift.append(self.source[i].redshift) return source_redshift - + @property def external_convergence(self): """ @@ -115,7 +115,8 @@ def einstein_radius(self): :return: Einstein radius [arc seconds] """ theta_E = theta_e_when_source_infinity( - v_sigma=self.deflector_velocity_dispersion()) + v_sigma=self.deflector_velocity_dispersion() + ) _, _, kappa_ext = self.los_linear_distortions return theta_E / (1 - kappa_ext) @@ -155,7 +156,7 @@ def _calculate_los_linear_distortions(self): :return: kappa, gamma1, gamma2 """ - if self.source_number==1: + if self.source_number == 1: source_z = self.source_redshift else: source_z = self.source_redshift[0] @@ -173,7 +174,6 @@ def deflector_magnitude(self, band): """ return self.deflector.magnitude(band=band) - def extended_source_magnitude(self, band): """Unlensed apparent magnitude of the extended source for a given band (assumes that size is the same for different bands) @@ -203,8 +203,9 @@ def lenstronomy_kwargs(self, band=None): ) = self.deflector.light_model_lenstronomy(band=band) sources, sources_kwargs = self.source_light_model_lenstronomy(band=band) - combined_lens_light_model_list = lens_light_model_list + sources[ - "source_light_model_list"] + combined_lens_light_model_list = ( + lens_light_model_list + sources["source_light_model_list"] + ) combined_kwargs_lens_light = kwargs_lens_light + sources_kwargs["kwargs_source"] kwargs_model = { @@ -274,38 +275,40 @@ def source_light_model_lenstronomy(self, band=None): """ source_models = {} all_source_kwarg_dict = {} - """if ( - self._source_type == "extended" - or self._source_type == "point_plus_extended" - ):""" - if self.source_number==1: - source_class=self.source + """If ( self._source_type == "extended". + + or self._source_type == "point_plus_extended" ): + """ + if self.source_number == 1: + source_class = self.source else: - source_class=self.source[0] + source_class = self.source[0] if source_class.light_profile == "single_sersic": source_models["source_light_model_list"] = [ - "SERSIC_ELLIPSE"]*self.source_number - #In this case we will consider a single source with double sersic profile. + "SERSIC_ELLIPSE" + ] * self.source_number + # In this case we will consider a single source with double sersic profile. else: - raise ValueError("Provided light profile is not supported. Supported" - " light profile is single_sersic") - if self.source_number==1: + raise ValueError( + "Provided light profile is not supported. Supported" + " light profile is single_sersic" + ) + if self.source_number == 1: kwargs_source = self.source.kwargs_extended_source_light( - draw_area=self.test_area, - center_lens=self.deflector_position, - band=band + draw_area=self.test_area, center_lens=self.deflector_position, band=band ) else: - kwargs_source=[] + kwargs_source = [] for i in range(self.source_number): - kwargs_source.append(self.source[i].kwargs_extended_source_light( - draw_area=self.test_area, - center_lens=self.deflector_position, - band=band - )[0]) + kwargs_source.append( + self.source[i].kwargs_extended_source_light( + draw_area=self.test_area, + center_lens=self.deflector_position, + band=band, + )[0] + ) - kwargs_ps = None all_source_kwarg_dict["kwargs_source"] = kwargs_source all_source_kwarg_dict["kwargs_ps"] = kwargs_ps - return source_models, all_source_kwarg_dict \ No newline at end of file + return source_models, all_source_kwarg_dict diff --git a/slsim/false_positive_pop.py b/slsim/false_positive_pop.py index ba011fb40..4d87d286f 100644 --- a/slsim/false_positive_pop.py +++ b/slsim/false_positive_pop.py @@ -20,7 +20,7 @@ def __init__( cosmo: Optional[Cosmology] = None, los_config: Optional[LOSConfig] = None, source_number_choice: Optional[list[int]] = [1, 2, 3], - weights_for_source_number: Optional[list[int]] = None + weights_for_source_number: Optional[list[int]] = None, ): """ Args: @@ -48,11 +48,11 @@ def draw_false_positive(self, number=1): """Draw given number of false positives within the cuts of the lens and source. :param number: number of false positive requested. The default value is 1. - :return: list of FalsePositive() instance with parameters of a pair of - elliptical and blue galaxy. + :return: list of FalsePositive() instance with parameters of a pair of + elliptical and blue galaxy. """ false_positive_population = [] - + for _ in range(number): successful = False while not successful: @@ -61,26 +61,31 @@ def draw_false_positive(self, number=1): lens = self._lens_galaxies.draw_deflector() _lens = Deflector( deflector_type=self._lens_galaxies.deflector_profile, - deflector_dict=lens) + deflector_dict=lens, + ) tolerance = 0.002 z_max = _lens.redshift + tolerance # Try to draw a source with the z_max based on the lens redshift and # source number choice. - source_number = random.choices( - self._choice, weights=self._weights)[0] - source_list=[] + source_number = random.choices(self._choice, weights=self._weights)[ + 0 + ] + source_list = [] for _ in range(source_number): source = self._sources.draw_source(z_max=z_max) - source_list.append(Source( - source_dict=source, - cosmo=self.cosmo, - source_type=self._sources.source_type, - light_profile=self._sources.light_profile)) - if source_number==1: - _source=source_list[0] + source_list.append( + Source( + source_dict=source, + cosmo=self.cosmo, + source_type=self._sources.source_type, + light_profile=self._sources.light_profile, + ) + ) + if source_number == 1: + _source = source_list[0] else: - _source=source_list - # Compute test area for false positive position. + _source = source_list + # Compute test area for false positive position. # This area will be used to determine the position of false positive. test_area = 3 * draw_test_area(deflector=lens) @@ -89,12 +94,12 @@ def draw_false_positive(self, number=1): deflector_class=_lens, source_class=_source, cosmo=self.cosmo, - test_area=test_area + test_area=test_area, ) # Add the false positive to the population false_positive_population.append(false_positive) - successful = True + successful = True except ValueError as e: # Handle the specific case where no sources are found for z_max diff --git a/slsim/lens.py b/slsim/lens.py index e69973216..290a01fa2 100644 --- a/slsim/lens.py +++ b/slsim/lens.py @@ -681,9 +681,7 @@ def source_light_model_lenstronomy(self, band=None): "SERSIC_ELLIPSE", ] kwargs_source = self.source.kwargs_extended_source_light( - draw_area=self.test_area, - center_lens=self.deflector_position, - band=band + draw_area=self.test_area, center_lens=self.deflector_position, band=band ) else: # source_models['source_light_model_list'] = None diff --git a/slsim/lens_pop.py b/slsim/lens_pop.py index 6c3c47c7d..66fb6283e 100644 --- a/slsim/lens_pop.py +++ b/slsim/lens_pop.py @@ -78,9 +78,8 @@ def select_lens_at_random(self, test_area=None, **kwargs_lens_cut): # TODO: make sure mass function is preserved, # as well as option to draw all lenses within the cuts within the area - :param test_area: area of disk around one lensing galaxies to be investigated - on (in arc-seconds^2). If None, computed using deflector's velocity - dispersion. + :param test_area: area of disk around one lensing galaxies to be investigated on + (in arc-seconds^2). If None, computed using deflector's velocity dispersion. :return: Lens() instance with parameters of the deflector and lens and source light """ @@ -90,7 +89,7 @@ def select_lens_at_random(self, test_area=None, **kwargs_lens_cut): if test_area is None: test_area = draw_test_area(deflector=lens) else: - test_area=test_area + test_area = test_area gg_lens = Lens( deflector_dict=lens, source_dict=source, diff --git a/slsim/lensed_system_base.py b/slsim/lensed_system_base.py index c4f79f296..3d900b77d 100644 --- a/slsim/lensed_system_base.py +++ b/slsim/lensed_system_base.py @@ -25,7 +25,7 @@ def __init__( agn_driving_variability_model=None, agn_driving_kwargs_variability=None, source_type="extended", - light_profile="single_sersic" + light_profile="single_sersic", ): """ :param source_dict: source properties @@ -87,7 +87,7 @@ def __init__( agn_driving_variability_model=agn_driving_variability_model, agn_driving_kwargs_variability=agn_driving_kwargs_variability, source_type=source_type, - light_profile=light_profile + light_profile=light_profile, ) self.deflector = Deflector( deflector_type=deflector_type, diff --git a/tests/test_Source/test_galaxies.py b/tests/test_Source/test_galaxies.py index ea9ce8420..531d8796f 100644 --- a/tests/test_Source/test_galaxies.py +++ b/tests/test_Source/test_galaxies.py @@ -286,7 +286,7 @@ def test_draw_source(self): galaxy_2 = self.galaxies.draw_source(z_max=1) assert len(galaxy) > 0 assert galaxy_1["n_sersic"] == 1 - assert galaxy_2["z"] < 1+0.002 + assert galaxy_2["z"] < 1 + 0.002 with pytest.raises(ValueError): self.galaxies5.draw_source() diff --git a/tests/test_Source/test_source.py b/tests/test_Source/test_source.py index 2d30a42ad..f238bc402 100644 --- a/tests/test_Source/test_source.py +++ b/tests/test_Source/test_source.py @@ -189,7 +189,7 @@ def setup_method(self): sn_type="Ia", lightcurve_time=np.linspace(-20, 50, 100), cosmo=cosmo, - light_profile="double_sersic" + light_profile="double_sersic", ) self.source4 = Source( self.source_dict3, @@ -210,7 +210,7 @@ def setup_method(self): sn_type="Ia", lightcurve_time=np.linspace(-20, 50, 100), cosmo=cosmo, - light_profile="double_sersic" + light_profile="double_sersic", ) self.source6 = Source( @@ -222,7 +222,7 @@ def setup_method(self): sn_type="Ia", lightcurve_time=np.linspace(-20, 50, 100), cosmo=cosmo, - light_profile="triplet" + light_profile="triplet", ) self.source7 = Source( @@ -627,13 +627,9 @@ def test_kwargs_extended_source_light_double_sersic(self): assert len(kwargs) == 2 assert all(isinstance(kwargs_item, dict) for kwargs_item in kwargs) with pytest.raises(ValueError): - self.source6.kwargs_extended_source_light( - center_lens, draw_area, band="i" - ) + self.source6.kwargs_extended_source_light(center_lens, draw_area, band="i") with pytest.raises(ValueError): - self.source5.kwargs_extended_source_light( - center_lens, draw_area, band="i" - ) + self.source5.kwargs_extended_source_light(center_lens, draw_area, band="i") with pytest.raises(ValueError): self.source10.kwargs_variability_extracted with pytest.raises(ValueError): diff --git a/tests/test_false_positive.py b/tests/test_false_positive.py index 5a6e7cc33..182ce3d51 100644 --- a/tests/test_false_positive.py +++ b/tests/test_false_positive.py @@ -18,48 +18,60 @@ ) kwargs_deflector_cut = {"band": "g", "band_max": 28, "z_min": 0.01, "z_max": 2.5} kwargs_source_cut = {"band": "g", "band_max": 28, "z_min": 0.1, "z_max": 5.0} + + def test_false_positive(): # Mock objects for source_class and deflector_class - - + # Initialize a cosmology instance cosmo = FlatLambdaCDM(H0=70, Om0=0.3) lens_galaxies = deflectors.EllipticalLensGalaxies( - galaxy_list=galaxy_simulation_pipeline.red_galaxies, - kwargs_cut=kwargs_deflector_cut, - kwargs_mass2light=0.1, - cosmo=cosmo, - sky_area=sky_area,) - source_galaxies=sources.Galaxies( + galaxy_list=galaxy_simulation_pipeline.red_galaxies, + kwargs_cut=kwargs_deflector_cut, + kwargs_mass2light=0.1, + cosmo=cosmo, + sky_area=sky_area, + ) + source_galaxies = sources.Galaxies( galaxy_list=galaxy_simulation_pipeline.blue_galaxies, kwargs_cut=kwargs_source_cut, cosmo=cosmo, sky_area=sky_area, catalog_type="skypy", ) - single_deflector=lens_galaxies.draw_deflector() - single_source1=source_galaxies.draw_source() - single_source2=source_galaxies.draw_source() - lens = Deflector(deflector_type="EPL", - deflector_dict=single_deflector) - source=Source(source_dict=single_source1, - cosmo=cosmo, - source_type="extended", - light_profile="single_sersic") - source2=Source(source_dict=single_source1, - cosmo=cosmo, - source_type="extended", - light_profile="double_sersic") - source_list=[Source(source_dict=single_source1, - cosmo=cosmo, - source_type="extended", - light_profile="single_sersic"), Source(source_dict=single_source2, - cosmo=cosmo, - source_type="extended", - light_profile="single_sersic")] + single_deflector = lens_galaxies.draw_deflector() + single_source1 = source_galaxies.draw_source() + single_source2 = source_galaxies.draw_source() + lens = Deflector(deflector_type="EPL", deflector_dict=single_deflector) + source = Source( + source_dict=single_source1, + cosmo=cosmo, + source_type="extended", + light_profile="single_sersic", + ) + source2 = Source( + source_dict=single_source1, + cosmo=cosmo, + source_type="extended", + light_profile="double_sersic", + ) + source_list = [ + Source( + source_dict=single_source1, + cosmo=cosmo, + source_type="extended", + light_profile="single_sersic", + ), + Source( + source_dict=single_source2, + cosmo=cosmo, + source_type="extended", + light_profile="single_sersic", + ), + ] # LOS configuration los_config = LOSConfig() - + # Create an instance of FalsePositive false_positive_instance_1 = FalsePositive( source_class=source, @@ -81,36 +93,66 @@ def test_false_positive(): test_area=4 * np.pi, los_config=los_config, ) - required_keys = {'magnitude', 'R_sersic', 'n_sersic', 'e1', 'e2', 'center_x', - 'center_y'} + required_keys = { + "magnitude", + "R_sersic", + "n_sersic", + "e1", + "e2", + "center_x", + "center_y", + } assert false_positive_instance_1.source_number == 1 assert false_positive_instance_2.source_number == 2 - assert false_positive_instance_1.lenstronomy_kwargs("i")[0][ - "lens_light_model_list"][0] == 'SERSIC_ELLIPSE' - assert len(false_positive_instance_2.lenstronomy_kwargs("i")[0][ - "lens_light_model_list"]) == 3 - assert len(false_positive_instance_2.lenstronomy_kwargs("i")[1][ - "kwargs_lens_light"]) == 3 + assert ( + false_positive_instance_1.lenstronomy_kwargs("i")[0]["lens_light_model_list"][0] + == "SERSIC_ELLIPSE" + ) + assert ( + len( + false_positive_instance_2.lenstronomy_kwargs("i")[0][ + "lens_light_model_list" + ] + ) + == 3 + ) + assert ( + len(false_positive_instance_2.lenstronomy_kwargs("i")[1]["kwargs_lens_light"]) + == 3 + ) assert len(false_positive_instance_2.deflector_position) == 2 assert false_positive_instance_2.deflector_redshift == single_deflector["z"] assert false_positive_instance_1.source_redshift == single_source1["z"] assert np.all(false_positive_instance_2.source_redshift) == np.all( - np.array([single_source1["z"], single_source2["z"]])) + np.array([single_source1["z"], single_source2["z"]]) + ) assert false_positive_instance_1.external_convergence < 0.1 assert false_positive_instance_1.external_shear < 0.2 assert false_positive_instance_1.einstein_radius < 2.5 - assert false_positive_instance_1.deflector_magnitude(band="i") ==\ - single_deflector["mag_i"] - assert false_positive_instance_1.extended_source_magnitude(band="i") ==\ - single_source1["mag_i"] + assert ( + false_positive_instance_1.deflector_magnitude(band="i") + == single_deflector["mag_i"] + ) + assert ( + false_positive_instance_1.extended_source_magnitude(band="i") + == single_source1["mag_i"] + ) assert len(false_positive_instance_1.deflector_ellipticity()) == 4 - assert false_positive_instance_1.deflector_stellar_mass() ==\ - single_deflector["stellar_mass"] - assert set(false_positive_instance_1.deflector_light_model_lenstronomy(band="i" - )[1][0].keys()) == required_keys + assert ( + false_positive_instance_1.deflector_stellar_mass() + == single_deflector["stellar_mass"] + ) + assert ( + set( + false_positive_instance_1.deflector_light_model_lenstronomy(band="i")[1][ + 0 + ].keys() + ) + == required_keys + ) with pytest.raises(ValueError): - false_positive_instance_3.source_light_model_lenstronomy(band="i") - - + false_positive_instance_3.source_light_model_lenstronomy(band="i") + + if __name__ == "__main__": - pytest.main() \ No newline at end of file + pytest.main() diff --git a/tests/test_false_positive_pop.py b/tests/test_false_positive_pop.py index d7928e2e8..2cefe5c18 100644 --- a/tests/test_false_positive_pop.py +++ b/tests/test_false_positive_pop.py @@ -22,31 +22,38 @@ kwargs_cut=kwargs_deflector_cut, kwargs_mass2light=0.1, cosmo=cosmo, - sky_area=sky_area,) -source_galaxies=sources.Galaxies( + sky_area=sky_area, +) +source_galaxies = sources.Galaxies( galaxy_list=blue_galaxy_list, kwargs_cut=kwargs_source_cut, cosmo=cosmo, sky_area=sky_area, catalog_type="skypy", ) + + def test_draw_false_positive_single(): fp_pop1 = FalsePositivePop( - elliptical_galaxy_population=lens_galaxies, - blue_galaxy_population=source_galaxies, - cosmo=cosmo, - source_number_choice=[1]) + elliptical_galaxy_population=lens_galaxies, + blue_galaxy_population=source_galaxies, + cosmo=cosmo, + source_number_choice=[1], + ) draw_fp1 = fp_pop1.draw_false_positive() assert isinstance(draw_fp1, object) + def test_draw_false_positive_multiple(): fp_pop2 = FalsePositivePop( - elliptical_galaxy_population=lens_galaxies, - blue_galaxy_population=source_galaxies, - cosmo=cosmo, - source_number_choice=[2]) + elliptical_galaxy_population=lens_galaxies, + blue_galaxy_population=source_galaxies, + cosmo=cosmo, + source_number_choice=[2], + ) draw_fp2 = fp_pop2.draw_false_positive(number=2) assert isinstance(draw_fp2, list) + if __name__ == "__main__": pytest.main() diff --git a/tests/test_lens_pop.py b/tests/test_lens_pop.py index 59e9ff481..9fde452ab 100644 --- a/tests/test_lens_pop.py +++ b/tests/test_lens_pop.py @@ -183,8 +183,9 @@ def test_cluster_lens_pop_instance(): ) kwargs_lens_cut = {} - pes_lens_class = cluster_lens_pop.select_lens_at_random(test_area=4*np.pi, - **kwargs_lens_cut) + pes_lens_class = cluster_lens_pop.select_lens_at_random( + test_area=4 * np.pi, **kwargs_lens_cut + ) assert pes_lens_class.deflector.deflector_type == "NFW_CLUSTER" kwargs_model, kwargs_params = pes_lens_class.lenstronomy_kwargs(band="g") assert len(kwargs_model["lens_model_list"]) >= 3 # halo, 1>= subhalo, LoS From 1f9bd1b41615c1c3503f6b1324639c22e2fb1727 Mon Sep 17 00:00:00 2001 From: narayan Date: Fri, 11 Oct 2024 13:53:49 -0400 Subject: [PATCH 25/87] minor change. --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 529a8d7b9..398bdd9b6 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.8] + python-version: [3.9] steps: - uses: actions/checkout@v4 From fc29a6282886da5294df0a105c2cf91b2b04f05a Mon Sep 17 00:00:00 2001 From: narayan Date: Fri, 11 Oct 2024 14:39:47 -0400 Subject: [PATCH 26/87] added a notebook --- notebooks/false_positive_generation.ipynb | 229 ++++++++++++++++++++++ 1 file changed, 229 insertions(+) create mode 100644 notebooks/false_positive_generation.ipynb diff --git a/notebooks/false_positive_generation.ipynb b/notebooks/false_positive_generation.ipynb new file mode 100644 index 000000000..71424a90b --- /dev/null +++ b/notebooks/false_positive_generation.ipynb @@ -0,0 +1,229 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 45, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "from astropy.cosmology import FlatLambdaCDM\n", + "from astropy.units import Quantity\n", + "from slsim.false_positive_pop import FalsePositivePop\n", + "import slsim.Pipelines as pipelines\n", + "import slsim.Sources as sources\n", + "import slsim.Deflectors as deflectors\n", + "from slsim.image_simulation import lens_image" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# False positive simulations\n", + "\n", + "This notebook walks through the basics of simulating a galaxy-galaxy strong lenses like false postive.\n", + "\n", + "The notebook goes in three steps:\n", + "\n", + "1. The populations of elliptical and blue galaxy is produced.\n", + "2. The given number of false positive is generated.\n", + "3. A random false positive is selected and visualized in an image.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# define a cosmology\n", + "cosmo = FlatLambdaCDM(H0=70, Om0=0.3)\n", + "\n", + "# define a sky area\n", + "sky_area = Quantity(value=0.1, unit=\"deg2\")\n", + "\n", + "\n", + "# define limits in the intrinsic deflector and source population (in addition to the skypy config\n", + "# file)\n", + "kwargs_deflector_cut = {\"band\": \"g\", \"band_max\": 28, \"z_min\": 0.01, \"z_max\": 2.5}\n", + "kwargs_source_cut = {\"band\": \"g\", \"band_max\": 28, \"z_min\": 0.1, \"z_max\": 5.0}" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "# Generate galaxy population using skypy pipeline.\n", + "galaxy_simulation_pipeline = pipelines.SkyPyPipeline(\n", + " skypy_config=None, sky_area=sky_area, filters=None, cosmo=cosmo\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/narayankhadka/slsim/slsim/Deflectors/elliptical_lens_galaxies.py:39: UserWarning: Angular size is converted to arcsec because provided input_catalog_type is skypy. If this is not correct, please refer to the documentation of the class you are using\n", + " galaxy_list = param_util.catalog_with_angular_size_in_arcsec(\n" + ] + } + ], + "source": [ + "# Initiate deflector population class.\n", + "lens_galaxies = deflectors.EllipticalLensGalaxies(\n", + " galaxy_list=galaxy_simulation_pipeline.red_galaxies,\n", + " kwargs_cut=kwargs_deflector_cut,\n", + " kwargs_mass2light=0.1,\n", + " cosmo=cosmo,\n", + " sky_area=sky_area,)\n", + "#Initialize source pospulation class\n", + "source_galaxies=sources.Galaxies(\n", + " galaxy_list=galaxy_simulation_pipeline.blue_galaxies,\n", + " kwargs_cut=kwargs_source_cut,\n", + " cosmo=cosmo,\n", + " sky_area=sky_area,\n", + " catalog_type=\"skypy\",\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "# make galaxy-galaxy lens like false positive class.\n", + "fp_pop = FalsePositivePop(\n", + " elliptical_galaxy_population=lens_galaxies,\n", + " blue_galaxy_population=source_galaxies,\n", + " cosmo=cosmo\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Generate given number of false positive" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "fp_class=fp_pop.draw_false_positive(number=100)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Simulate image in i-band" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": {}, + "outputs": [], + "source": [ + "# load your psf kernel and transform matrix. If you have your own psf, please provide\n", + "# it here.\n", + "path = \"../tests/TestData/psf_kernels_for_deflector.npy\"\n", + "psf_kernel = 1 * np.load(path)\n", + "psf_kernel[psf_kernel < 0] = 0\n", + "transform_matrix = np.array([[0.2, 0], [0, 0.2]])" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": {}, + "outputs": [], + "source": [ + "image_false_positive_i = lens_image(\n", + " lens_class=fp_class[18],\n", + " band=\"i\",\n", + " mag_zero_point=31,\n", + " num_pix=64,\n", + " psf_kernel=psf_kernel,\n", + " transform_pix2angle=transform_matrix,\n", + " exposure_time=30,\n", + " with_deflector=True,\n", + " with_source=False,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAPsAAAD4CAYAAAAq5pAIAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/TGe4hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAU2klEQVR4nO3dbYwd1X3H8e/Pu2svNhDzFMvBJNCWBqG2QLQlQYmiBpKUplHgRYTyoNaqLPlNWhElUgqtVClSVSVv8vCiInJDEr9IA5Q8gFCahDhEVarKiQkkAZwUh4KwYzAEHIwxftp/X9yx75mze2dn794Hr8/vI6127p25M/+9d/93/mfOzBlFBGZ2+lsx7gDMbDSc7GaFcLKbFcLJblYIJ7tZISZHubGVWhXTrBnlJs2K8ioHORKHNd+8kSb7NGt4s64b5SbNirI9tvWc5zLerBBOdrNCONnNCuFkNyuEk92sEE52s0I42c0K0SrZJa2VdLekX0raKekaSedKul/S49Xvc4YdrJn1r+2e/fPAdyLiMuAKYCdwC7AtIi4FtlWPzewUtWCyS3oN8HbgdoCIOBIR+4EbgK3VYluBG4cTopkNQps9+yXAc8CXJT0k6YuS1gDrImJvtcwzwLr5Xixps6QdknYc5fBgojazRWuT7JPAm4DbIuIq4CBZyR6dsa3mHd8qIrZExExEzEyxaqnxmlmf2iT7bmB3RGyvHt9NJ/mflbQeoPq9bzghmtkgLJjsEfEM8LSkN1ZPXQc8BtwLbKye2wjcM5QIzWwg2l7i+nfAVyWtBJ4A/obOF8VdkjYBTwE3DSdEMxuEVskeEQ8DM/PM8sXpZsuEz6AzK4ST3awQTnazQjjZzQrhZDcrhJPdrBBOdrNCONnNCuFkNyuEk92sEE52s0I42c0K4WQ3K4ST3awQTnazQjjZzQrhZDcrhJPdrBBOdrNCONnNCuFkNyuEk92sEE52s0I42c0K4WQ3K0SrO8JIehI4ABwHjkXEjKRzgTuBi4EngZsi4sXhhGlmS7WYPfs7IuLKiDhxG6hbgG0RcSmwjew2zmZ2allKGX8DsLWa3grcuORozGxo2iZ7AN+T9KCkzdVz6yJibzX9DLBuvhdK2ixph6QdRzm8xHDNrF9tb9n8tojYI+m1wP2SfpnOjIiQFPO9MCK2AFsAzta58y5jZsPXas8eEXuq3/uAbwJXA89KWg9Q/d43rCDNbOkWTHZJaySddWIaeDfwCHAvsLFabCNwz7CCNLOla1PGrwO+KenE8v8eEd+R9BPgLkmbgKeAm4YXppkt1YLJHhFPAFfM8/xvgeuGEZQtQudLeH7hQyTW5TPozArhZDcrhJPdrBBt+9ltnBbTLu+1bClt+xUT9cezx7vT6XtwOv3NLXnPblYIJ7tZIVzGj1pTOd3zNdl3csz2Xl9t2VmK0PSepvPS9yZ/SVrun6a8ZzcrhJPdrBAu44etscRckT1sW+J3jzjH8Xr5ma4jZpP1R0NJnx/Bblq2ttyYjmg3Nl2yWRMTrZar/SVNf/8yPorvPbtZIZzsZoVwspsVwm32YWjZTm9qo2uy+9FE1k5Uuv6J/Iyx7rJKZ0V9W5EsN6eNqpZt/VGeldeyy1L5+5G8x+m8ucc6kvf76LH6Ok6Tbjnv2c0K4WQ3K4TL+H61vKhiTlmZzpvM3v5kWa1c2Z3OX5iU1vm8OJ6U3UmpOqdsTccHbSrxW5f0+X4jWTZ9f/JyvKnc71W6582ayeR9m6q/p1o5Nf86jmefS/r+ZO9VJF2dy7mk957drBBOdrNCONnNCuE2+yAs5vTNpJ2etssBtGZ198H0qpOT0avdCXPbr787kITVjWv24Cv1lx050n0wm8fbo13a8He1PsW2SVN7Pp23mNN70+MPSXte2d8Ss+mVhPlVhkd7r38Z8Z7drBBOdrNCuIwfAE32LrOVlOOdZSd7zouzzzw5fez8ZHpN/WM6vrL7HT15qF5yT5zXfd3kCwdPTq/ISuQ4mFwdl5b0DfLuu9aauinbzqsFkpftaddYQ0lf64pcRLOjaUCQZXQVnPfsZoVoneySJiQ9JOm+6vElkrZL2iXpTkkrF1qHmY3PYsr4m4GdwNnV408Dn42IOyR9AdgE3Dbg+JaFvLxd0XD0PC3dj7/uvNq8I+edcXL6pTd013Hogno5e2Rtt3ScOlD/CM96svude/aT3e/yqfyim2PdmOcUy0lZH8e6F4U0XbgTc47o12Z2p9uOF9ckK53TM/7iSP3IeS2qFQ0xJuV/Y3NlGZXtuVZ7dkkbgL8Evlg9FnAtcHe1yFbgxiHEZ2YD0raM/xzwCbpHJ84D9kfEia/93cCF871Q0mZJOyTtOMrhpcRqZkvQ5v7s7wX2RcSD/WwgIrZExExEzEyxauEXmNlQtGmzvxV4n6T3ANN02uyfB9ZKmqz27huAPcMLc0z6GeOd+mATeZcXZ0yfnDy+un5M8+XXddvpL/xxtw35h3+0u7bca8/oniW379BZtXm7HrqoG8dEd1uvyWKcStrs7M9mJu302uAY+dVgsy3br22vnGvb9Tbndb3PfptN2vC1KxAb41i+3WtNFtyzR8StEbEhIi4GPgD8ICI+DDwAvL9abCNwz9CiNLMlW0o/+98DH5O0i04b/vbBhGRmw7CoM+gi4ofAD6vpJ4CrBx/SKaRlWZl3SdXKxWwwhZjulu6zK+vftWkX25mvf+nk9Idet7223F+f/fzJ6a+/fHZt3r8cvP7k9MsvnH9yevVz9TgmX+zGsWIi+85PB9VIu6SygThqY9w1dFf1HAyjM7M73XQH1tqGG8r7/DXJsnGs6WKXlmPtLeOS3mfQmRXCyW5WCCe7WSF81Vu/mu4blow7rtVZe/5Q98SiUL3bbPJQd/rgq91uuDUrel+V9qfTv6k9Pnu6u/6Dq7rty9mprJ07kbRl83ZoegxiqhuH8q639Gq5fGDNZNn6/efy4yDJ+9jQ3q5vuOFYStsBMJos43Z5E+/ZzQrhZDcrhMv4xaiVd2lXTe+x4cmuwuLQqycnV/721dqs6Re6JfOB3d3x6D591p/Xltv1+odPTv/3C79fm/fUnqS7LRmgYsXhhrHts7Hwale6pWV3Ps59+pp8AIyezZysVG97RVw/5Xi+bLqOWL7jv/fLe3azQjjZzQrhMr5Jy1sV5UeYtaJhIISkRJ743cHarDOfTs5qO9a9QvB3z19QW+7fznvnyenJV+oxnrW/O73mN904pg7UmxN6tffwyDqjO4hGpD0B2dHytsV0+h4oa/K0PqttlHeMPU15z25WCCe7WSGc7GaFcJu9Sdu2YHbFVG3wxbzrLR308KUDtVlTq7pdb2e93G0rr9p/Rm25Y6u761B2SGAiGUd+1fPdU/J0NFswHUN9OrsNVXoGoJLbUOXda+mxinTAi1zTIBf9DmxRW8cArko7Ta5sa+I9u1khnOxmhXAZ36/GsjKZzsdaP5qU9fmgEc+90J2VXIAyfXBNtv6GbqjJbtdWetFN4+ASq6frj5N1pM0OHcr+XaaSvyVvyuTNlx7LNY0f13PwijnrHEDZfZqW7inv2c0K4WQ3K4ST3awQbrMPwpzTapPTZY9mXVLpQA4H6+1XJfeIi+TqOL2a3Uknbevn7dx0/em68/ucJfeci6n6Kayz08mAFUm32Ypj2emy6XGA/Iq4/O8+sa2mwStybbvDCug2GwTv2c0K4WQ3K4TL+EFoGhMt75JKKl9N1r9re3ZX5d1maUmej+WebjudtyoboCJpCsTK+r/B7Mrkdcn6VhzK4kvL+obyWXkXYxpH+re1HYNuzkpcurfhPbtZIdrcxXVa0o8l/UzSo5I+WT1/iaTtknZJulPSyoXWZWbj06aMPwxcGxEvS5oCfiTpP4GPAZ+NiDskfQHYBNw2xFjHr+3Qxi3HS8sHtlA+HPPJ5bKzzpLHms2aCck60i3nw0XrcLckX3EwO9rfy2LK5fRCm9rYb01n0DWU7T7ivmRt7uIaEfFy9XCq+gngWuDu6vmtwI3DCNDMBqNVm13ShKSHgX3A/cCvgf3VvdkBdgMX9njtZkk7JO04Sss9iJkNXKtkj4jjEXElsIHOnVsva7uBiNgSETMRMTPFqoVfYGZDsdhbNu+X9ABwDbBW0mS1d98A7BlGgKeUftqK/XbL5VfL1daRdJvlbfHkcTpPebdecsabXqmPX1/bA6THB/Kz4tLjBU3dg/mgF7WABzB4hbXS5mj8BZLWVtNnAO8CdgIPAO+vFtsI3DOkGM1sANrs2dcDWyVN0PlyuCsi7pP0GHCHpH8GHgJuH2KcZrZECyZ7RPwcuGqe55+g0363Jou5VVHMf0uppnHp59xNKV2ux/MAvJKMTzedHUtJLrxJL6BpGoMusot1at2FabMjL/ebSvdeWo7nb3U+g86sEE52s0I42c0K4avehq3toAvQs23b6zTaeaXt6OPdNraywSVqUdV73mqDY8Th3idCpe30vAuwZ1s8f75t15stmffsZoVwspsVwmX8OM0p8Xt0V+XjtjWUu0or/qbx3dKr0vLusF5XmM323m4+1l56BmAt/qaYms42bFrOWvGe3awQTnazQriMP1W1PDKdH6lPS+a0pI/8LquLOcLfS9pjkF24Uyvd+z3K7nJ9oLxnNyuEk92sEE52s0K4zb7MzemWS+c1tKmbbuHca/2N7fK5L+zxfMvutXye2+9L5j27WSGc7GaFcBl/KmlbqjaVvuktlJSexdbf93rPM+E6TzS9cP7lFjWYh0v3QfKe3awQTnazQjjZzQrhNvty0NTOnTMYRK8rxfo7ZTXa9dC1357b4WPjPbtZIZzsZoVwGX+qanv22GK6snrJ178iuSKuR1feguvoxWO+j4337GaFaHOvt4skPSDpMUmPSrq5ev5cSfdLerz6fc7wwzWzfrXZsx8DPh4RlwNvAT4i6XLgFmBbRFwKbKse26BEdH8GsY78JyXVf2K2+5M+3yRfR6/XNcVhQ7VgskfE3oj4aTV9gM4dXC8EbgC2VottBW4cUoxmNgCLOkAn6WI6N3ncDqyLiL3VrGeAdT1esxnYDDDN6r4DNbOlaX2ATtKZwNeBj0bES+m86NwOZN6aLCK2RMRMRMxMsWq+RcxsBFrt2SVN0Un0r0bEN6qnn5W0PiL2SloP7BtWkMVbTHdVOq9tV1nb7rtBtLHd9TY2bY7GC7gd2BkRn0lm3QtsrKY3AvcMPjwzG5Q2e/a3An8F/ELSw9Vz/wB8CrhL0ibgKeCmoURoZgOxYLJHxI+AXnXedYMNx+bVtmxfzLxRnqGXctk+Nj6DzqwQTnazQjjZzQrhq95K1bbt7Db2acN7drNCONnNCuEy3vrn2zMtK96zmxXCyW5WCCe7WSHcZrf+uZ2+rHjPblYIJ7tZIVzGLwce8MEGwHt2s0I42c0K4TJ+OXDZbgPgPbtZIZzsZoVwspsVwsluVggnu1khnOxmhXCymxXCyW5WiDb3evuSpH2SHkmeO1fS/ZIer36fM9wwzWyp2uzZvwJcnz13C7AtIi4FtlWPzewUtmCyR8R/AS9kT98AbK2mtwI3DjYsMxu0fs+NXxcRe6vpZ4B1vRaUtBnYDDDN6j43Z2ZLteQDdBERQM8rNSJiS0TMRMTMFKuWujkz61O/yf6spPUA1e99gwvJzIah32S/F9hYTW8E7hlMOGY2LG263r4G/A/wRkm7JW0CPgW8S9LjwDurx2Z2ClvwAF1EfLDHrOsGHIuZDZHPoDMrhJPdrBBOdrNCONnNCuFkNyuEk92sEE52s0I42c0K4WQ3K4ST3awQTnazQjjZzQrhZDcrhJPdrBBOdrNCONnNCuFkNyuEk92sEE52s0I42c0K4WQ3K4ST3awQTnazQjjZzQrhZDcrxJKSXdL1kn4laZekWwYVlJkNXt/JLmkC+FfgL4DLgQ9KunxQgZnZYC1lz341sCsinoiII8AdwA2DCcvMBm0pyX4h8HTyeHf1XI2kzZJ2SNpxlMNL2JyZLcXQD9BFxJaImImImSlWDXtzZtbDgrdsbrAHuCh5vKF6rqcDvPj89+Pup4DzgeeXsO1BOBViAMeRcxx1i43jDb1mKCL6ikDSJPC/dO7Tvgf4CfChiHi0xWt3RMRMXxsekFMhBsfhOEYZR9979og4Julvge8CE8CX2iS6mY3HUsp4IuLbwLcHFIuZDdG4zqDbMqbtpk6FGMBx5BxH3cDi6LvNbmbLi8+NNyuEk92sECNN9nFdOCPpS5L2SXokee5cSfdLerz6fc4I4rhI0gOSHpP0qKSbxxGLpGlJP5b0syqOT1bPXyJpe/X53Clp5TDjSOKZkPSQpPvGFYekJyX9QtLDknZUz43jf2StpLsl/VLSTknXDCqOkSX7mC+c+QpwffbcLcC2iLgU2FY9HrZjwMcj4nLgLcBHqvdg1LEcBq6NiCuAK4HrJb0F+DTw2Yj4A+BFYNOQ4zjhZmBn8nhccbwjIq5M+rXH8T/yeeA7EXEZcAWd92UwcUTESH6Aa4DvJo9vBW4d4fYvBh5JHv8KWF9Nrwd+NapYkhjuAd41zliA1cBPgTfTOVNrcr7Pa4jb31D9A18L3AdoTHE8CZyfPTfSzwV4DfB/VAfOBx3HKMv4VhfOjNC6iNhbTT8DrBvlxiVdDFwFbB9HLFXp/DCwD7gf+DWwPyKOVYuM6vP5HPAJYLZ6fN6Y4gjge5IelLS5em7Un8slwHPAl6tmzRclrRlUHD5AB0TnK3NkfZCSzgS+Dnw0Il4aRywRcTwirqSzZ70auGzY28xJei+wLyIeHPW25/G2iHgTnWbmRyS9PZ05os9lEngTcFtEXAUcJCvZlxLHKJN90RfODNmzktYDVL/3jWKjkqboJPpXI+Ib44wFICL2Aw/QKZfXVtc8wGg+n7cC75P0JJ3xEK6l02YddRxExJ7q9z7gm3S+AEf9uewGdkfE9urx3XSSfyBxjDLZfwJcWh1pXQl8ALh3hNvP3QtsrKY30mk/D5UkAbcDOyPiM+OKRdIFktZW02fQOW6wk07Sv39UcUTErRGxISIupvP/8IOI+PCo45C0RtJZJ6aBdwOPMOLPJSKeAZ6W9MbqqeuAxwYWx7APfGQHGt5D50q5XwP/OMLtfg3YCxyl8+25iU7bcBvwOPB94NwRxPE2OiXYz4GHq5/3jDoW4E+Ah6o4HgH+qXr+94AfA7uA/wBWjfAz+jPgvnHEUW3vZ9XPoyf+N8f0P3IlsKP6bL4FnDOoOHy6rFkhfIDOrBBOdrNCONnNCuFkNyuEk92sEE52s0I42c0K8f9GUlGPl1s/vwAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plt.imshow(image_false_positive_i, origin=\"lower\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "base", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From d7c71ac62df13d258c1e8358f2a8c0e3150df081 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 11 Oct 2024 18:40:19 +0000 Subject: [PATCH 27/87] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- notebooks/false_positive_generation.ipynb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/notebooks/false_positive_generation.ipynb b/notebooks/false_positive_generation.ipynb index 71424a90b..be3e87b94 100644 --- a/notebooks/false_positive_generation.ipynb +++ b/notebooks/false_positive_generation.ipynb @@ -84,9 +84,10 @@ " kwargs_cut=kwargs_deflector_cut,\n", " kwargs_mass2light=0.1,\n", " cosmo=cosmo,\n", - " sky_area=sky_area,)\n", - "#Initialize source pospulation class\n", - "source_galaxies=sources.Galaxies(\n", + " sky_area=sky_area,\n", + ")\n", + "# Initialize source pospulation class\n", + "source_galaxies = sources.Galaxies(\n", " galaxy_list=galaxy_simulation_pipeline.blue_galaxies,\n", " kwargs_cut=kwargs_source_cut,\n", " cosmo=cosmo,\n", @@ -105,7 +106,7 @@ "fp_pop = FalsePositivePop(\n", " elliptical_galaxy_population=lens_galaxies,\n", " blue_galaxy_population=source_galaxies,\n", - " cosmo=cosmo\n", + " cosmo=cosmo,\n", ")" ] }, @@ -122,7 +123,7 @@ "metadata": {}, "outputs": [], "source": [ - "fp_class=fp_pop.draw_false_positive(number=100)" + "fp_class = fp_pop.draw_false_positive(number=100)" ] }, { From f96ceb00ba6cda8bb32152519d0155f57cc2dc3c Mon Sep 17 00:00:00 2001 From: narayan Date: Fri, 11 Oct 2024 15:29:20 -0400 Subject: [PATCH 28/87] improved test function in galaxies --- tests/test_Source/test_galaxies.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_Source/test_galaxies.py b/tests/test_Source/test_galaxies.py index 531d8796f..15e75aeee 100644 --- a/tests/test_Source/test_galaxies.py +++ b/tests/test_Source/test_galaxies.py @@ -289,6 +289,8 @@ def test_draw_source(self): assert galaxy_2["z"] < 1 + 0.002 with pytest.raises(ValueError): self.galaxies5.draw_source() + with pytest.raises(ValueError): + self.galaxies5.draw_source(z_max=0.4) def test_draw_source_double_sersic(self): galaxy1 = self.galaxies2.draw_source() From f66c0d9b5dbcfd9876dd4129ad4b467ba56d8038 Mon Sep 17 00:00:00 2001 From: narayan Date: Fri, 11 Oct 2024 15:34:47 -0400 Subject: [PATCH 29/87] created a separate false positive folder. --- slsim/{ => FalsePositives}/false_positive.py | 0 slsim/{ => FalsePositives}/false_positive_pop.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename slsim/{ => FalsePositives}/false_positive.py (100%) rename slsim/{ => FalsePositives}/false_positive_pop.py (100%) diff --git a/slsim/false_positive.py b/slsim/FalsePositives/false_positive.py similarity index 100% rename from slsim/false_positive.py rename to slsim/FalsePositives/false_positive.py diff --git a/slsim/false_positive_pop.py b/slsim/FalsePositives/false_positive_pop.py similarity index 100% rename from slsim/false_positive_pop.py rename to slsim/FalsePositives/false_positive_pop.py From fff317fa37112efef889e4254e8cbfb38622a23d Mon Sep 17 00:00:00 2001 From: narayan Date: Fri, 11 Oct 2024 15:35:41 -0400 Subject: [PATCH 30/87] minor change. --- slsim/FalsePositives/false_positive_pop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slsim/FalsePositives/false_positive_pop.py b/slsim/FalsePositives/false_positive_pop.py index 4d87d286f..567e8de9f 100644 --- a/slsim/FalsePositives/false_positive_pop.py +++ b/slsim/FalsePositives/false_positive_pop.py @@ -1,4 +1,4 @@ -from slsim.false_positive import FalsePositive +from slsim.FalsePositives.false_positive import FalsePositive from typing import Optional from astropy.cosmology import Cosmology from slsim.Sources.source_pop_base import SourcePopBase From bb5cf288cf52588eb6859a7008dfd6b64528294d Mon Sep 17 00:00:00 2001 From: narayan Date: Fri, 11 Oct 2024 15:38:04 -0400 Subject: [PATCH 31/87] minor change --- tests/{ => test_FalsePositives}/test_false_positive.py | 0 tests/{ => test_FalsePositives}/test_false_positive_pop.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename tests/{ => test_FalsePositives}/test_false_positive.py (100%) rename tests/{ => test_FalsePositives}/test_false_positive_pop.py (100%) diff --git a/tests/test_false_positive.py b/tests/test_FalsePositives/test_false_positive.py similarity index 100% rename from tests/test_false_positive.py rename to tests/test_FalsePositives/test_false_positive.py diff --git a/tests/test_false_positive_pop.py b/tests/test_FalsePositives/test_false_positive_pop.py similarity index 100% rename from tests/test_false_positive_pop.py rename to tests/test_FalsePositives/test_false_positive_pop.py From b33047cd3b148cc47150149c09baa429753441be Mon Sep 17 00:00:00 2001 From: narayan Date: Fri, 11 Oct 2024 15:38:54 -0400 Subject: [PATCH 32/87] minor change --- tests/test_FalsePositives/test_false_positive.py | 2 +- tests/test_FalsePositives/test_false_positive_pop.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_FalsePositives/test_false_positive.py b/tests/test_FalsePositives/test_false_positive.py index 182ce3d51..9ed617f67 100644 --- a/tests/test_FalsePositives/test_false_positive.py +++ b/tests/test_FalsePositives/test_false_positive.py @@ -7,7 +7,7 @@ import slsim.Sources as sources import slsim.Deflectors as deflectors import slsim.Pipelines as pipelines -from slsim.false_positive import FalsePositive +from slsim.FalsePositives.false_positive import FalsePositive from astropy.units import Quantity sky_area = Quantity(value=0.01, unit="deg2") diff --git a/tests/test_FalsePositives/test_false_positive_pop.py b/tests/test_FalsePositives/test_false_positive_pop.py index 2cefe5c18..cbd9b419c 100644 --- a/tests/test_FalsePositives/test_false_positive_pop.py +++ b/tests/test_FalsePositives/test_false_positive_pop.py @@ -3,7 +3,7 @@ import slsim.Sources as sources import slsim.Deflectors as deflectors import slsim.Pipelines as pipelines -from slsim.false_positive_pop import FalsePositivePop +from slsim.FalsePositives.false_positive_pop import FalsePositivePop from astropy.units import Quantity sky_area = Quantity(value=0.01, unit="deg2") From 5755ee28a72d863c004ccdaea2b0ffbedf542404 Mon Sep 17 00:00:00 2001 From: narayan Date: Fri, 11 Oct 2024 16:49:36 -0400 Subject: [PATCH 33/87] added a test function --- slsim/Sources/source.py | 22 ++++++++++++++++++++++ slsim/lens.py | 11 +++-------- slsim/lens_pop.py | 2 +- tests/test_Source/test_source.py | 22 +++++++++++++++++++++- 4 files changed, 47 insertions(+), 10 deletions(-) diff --git a/slsim/Sources/source.py b/slsim/Sources/source.py index 7767dc316..d9341b390 100644 --- a/slsim/Sources/source.py +++ b/slsim/Sources/source.py @@ -577,6 +577,28 @@ def kwargs_extended_source_light(self, center_lens, draw_area, band=None): raise ValueError("Provided sersic profile is not supported.") return kwargs_extended_source + def extended_source_light_model(self): + """Provides a list of source models. + + :return: list of extented source model. + """ + if ( + self.source_type == "extended" + or self.source_type == "point_plus_extended" + ): + if self.light_profile == "single_sersic": + source_models_list = ["SERSIC_ELLIPSE"] + elif self.light_profile == "double_sersic": + source_models_list = [ + "SERSIC_ELLIPSE", + "SERSIC_ELLIPSE", + ] + else: + raise ValueError("Provided sersic profile is not supported. " + "Supported profiles are single_sersic and double_sersic.") + else: + source_models_list = None + return source_models_list def extract_agn_kwargs_from_source_dict(source_dict): """This extracts all AGN related parameters from a source_dict Table and constructs diff --git a/slsim/lens.py b/slsim/lens.py index 290a01fa2..432fe889c 100644 --- a/slsim/lens.py +++ b/slsim/lens.py @@ -74,7 +74,7 @@ def __init__( :type sn_absolute_mag_band: str or `~sncosmo.Bandpass` :param sn_absolute_zpsys: Optional, AB or Vega (AB default) :type sn_absolute_zpsys: str - :param test_area: area of disk around one lensing galaxies to be investigated + :param test_area: solid angle around one lensing galaxies to be investigated on (in arc-seconds^2) :param magnification_limit: absolute lensing magnification lower limit to register a point source (ignore highly de-magnified images) @@ -673,13 +673,8 @@ def source_light_model_lenstronomy(self, band=None): self._source_type == "extended" or self._source_type == "point_plus_extended" ): - if self.source.light_profile == "single_sersic": - source_models["source_light_model_list"] = ["SERSIC_ELLIPSE"] - else: - source_models["source_light_model_list"] = [ - "SERSIC_ELLIPSE", - "SERSIC_ELLIPSE", - ] + source_models["source_light_model_list" + ] = self.source.extended_source_light_model() kwargs_source = self.source.kwargs_extended_source_light( draw_area=self.test_area, center_lens=self.deflector_position, band=band ) diff --git a/slsim/lens_pop.py b/slsim/lens_pop.py index 66fb6283e..8e755d087 100644 --- a/slsim/lens_pop.py +++ b/slsim/lens_pop.py @@ -78,7 +78,7 @@ def select_lens_at_random(self, test_area=None, **kwargs_lens_cut): # TODO: make sure mass function is preserved, # as well as option to draw all lenses within the cuts within the area - :param test_area: area of disk around one lensing galaxies to be investigated on + :param test_area: solid angle around one lensing galaxies to be investigated on (in arc-seconds^2). If None, computed using deflector's velocity dispersion. :return: Lens() instance with parameters of the deflector and lens and source light diff --git a/tests/test_Source/test_source.py b/tests/test_Source/test_source.py index f238bc402..b9d21394c 100644 --- a/tests/test_Source/test_source.py +++ b/tests/test_Source/test_source.py @@ -516,7 +516,18 @@ def setup_method(self): agn_driving_variability_model="bending_power_law", agn_driving_kwargs_variability=variable_agn_kwarg_dict, ) - + self.source_light_model_1=Source(source_dict=self.source_dict, + cosmo=cosmo, + source_type="extended", + light_profile="single_sersic") + self.source_light_model_2=Source(source_dict=self.source_dict3, + cosmo=cosmo, + source_type="extended", + light_profile="double_sersic") + self.source_light_model_3=Source(source_dict=self.source_dict3, + cosmo=cosmo, + source_type="extended", + light_profile="triple_sersic") def test_redshift(self): assert self.source.redshift == [0.5] assert self.source11.redshift == 3.123 @@ -705,6 +716,15 @@ def test_add_mean_mag_to_source_table(self): assert final_source_table["ps_mag_i"] == 23 assert final_source_table["ps_mag_r"] == 24 assert final_source_table["ps_mag_g"] == 25 + def test_extended_source_light_model(self): + light_model1=self.source_light_model_1.extended_source_light_model() + light_model2=self.source_light_model_2.extended_source_light_model() + assert light_model1[0]=="SERSIC_ELLIPSE" + assert len(light_model2)==2 + assert light_model2[0]=="SERSIC_ELLIPSE" + with pytest.raises(ValueError): + self.source_light_model_3.extended_source_light_model() + if __name__ == "__main__": From a075196d68547a8225283644c5e7c8b5c8657d18 Mon Sep 17 00:00:00 2001 From: narayan Date: Fri, 11 Oct 2024 16:55:53 -0400 Subject: [PATCH 34/87] minor change. --- notebooks/false_positive_generation.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notebooks/false_positive_generation.ipynb b/notebooks/false_positive_generation.ipynb index 71424a90b..72d60cc49 100644 --- a/notebooks/false_positive_generation.ipynb +++ b/notebooks/false_positive_generation.ipynb @@ -10,7 +10,7 @@ "import matplotlib.pyplot as plt\n", "from astropy.cosmology import FlatLambdaCDM\n", "from astropy.units import Quantity\n", - "from slsim.false_positive_pop import FalsePositivePop\n", + "from slsim.FalsePositives.false_positive_pop import FalsePositivePop\n", "import slsim.Pipelines as pipelines\n", "import slsim.Sources as sources\n", "import slsim.Deflectors as deflectors\n", From 223c89593254fcec21d7a589424b2d067d0daa67 Mon Sep 17 00:00:00 2001 From: narayan Date: Fri, 11 Oct 2024 17:19:16 -0400 Subject: [PATCH 35/87] minor change. --- slsim/Sources/galaxies.py | 8 +++++--- tests/test_Source/test_galaxies.py | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/slsim/Sources/galaxies.py b/slsim/Sources/galaxies.py index fa2674a6e..146f13c40 100644 --- a/slsim/Sources/galaxies.py +++ b/slsim/Sources/galaxies.py @@ -124,15 +124,17 @@ def draw_source(self, z_max=None): """Choose source at random. :param z_max: maximum redshift for source to be drawn. + :param z_max: maximum redshift limit for the galaxy to be drawn. If no galaxy is + found for this limit, None will be returned. :return: dictionary of source """ if z_max is not None: filtered_galaxies = self._galaxy_select[self._galaxy_select["z"] < z_max] - if len(filtered_galaxies) > 0: + if len(filtered_galaxies) == 0: + return None + else: index = random.randint(0, len(filtered_galaxies) - 1) galaxy = filtered_galaxies[index] - else: - raise ValueError(f"No galaxies found with z < {z_max}.") else: index = random.randint(0, self._num_select - 1) galaxy = self._galaxy_select[index] diff --git a/tests/test_Source/test_galaxies.py b/tests/test_Source/test_galaxies.py index 15e75aeee..5c9bad6c4 100644 --- a/tests/test_Source/test_galaxies.py +++ b/tests/test_Source/test_galaxies.py @@ -284,13 +284,13 @@ def test_draw_source(self): galaxy = self.galaxies.draw_source() galaxy_1 = self.galaxies4.draw_source() galaxy_2 = self.galaxies.draw_source(z_max=1) + galaxy_3 = self.galaxies5.draw_source(z_max=0.4) assert len(galaxy) > 0 assert galaxy_1["n_sersic"] == 1 assert galaxy_2["z"] < 1 + 0.002 with pytest.raises(ValueError): self.galaxies5.draw_source() - with pytest.raises(ValueError): - self.galaxies5.draw_source(z_max=0.4) + assert galaxy_3 is None def test_draw_source_double_sersic(self): galaxy1 = self.galaxies2.draw_source() From 63c6056ec648d89d7b61547c26141a8459149406 Mon Sep 17 00:00:00 2001 From: narayan Date: Tue, 15 Oct 2024 14:34:45 -0400 Subject: [PATCH 36/87] minor change --- slsim/FalsePositives/false_positive.py | 3 +- slsim/FalsePositives/false_positive_pop.py | 91 +++++++++++----------- 2 files changed, 48 insertions(+), 46 deletions(-) diff --git a/slsim/FalsePositives/false_positive.py b/slsim/FalsePositives/false_positive.py index 39679e9b2..52233df8d 100644 --- a/slsim/FalsePositives/false_positive.py +++ b/slsim/FalsePositives/false_positive.py @@ -1,11 +1,12 @@ import numpy as np from lenstronomy.Cosmo.lens_cosmo import LensCosmo from slsim.ParamDistributions.los_config import LOSConfig +from slsim.lens import Lens from slsim.Util.param_util import ellipticity_slsim_to_lenstronomy from slsim.lens import theta_e_when_source_infinity -class FalsePositive(object): +class FalsePositive(Lens): """Class to manage individual false positive.""" def __init__( diff --git a/slsim/FalsePositives/false_positive_pop.py b/slsim/FalsePositives/false_positive_pop.py index 567e8de9f..56464e5d4 100644 --- a/slsim/FalsePositives/false_positive_pop.py +++ b/slsim/FalsePositives/false_positive_pop.py @@ -56,55 +56,56 @@ def draw_false_positive(self, number=1): for _ in range(number): successful = False while not successful: - try: - # Sample a lens (deflector) - lens = self._lens_galaxies.draw_deflector() - _lens = Deflector( - deflector_type=self._lens_galaxies.deflector_profile, - deflector_dict=lens, - ) - tolerance = 0.002 - z_max = _lens.redshift + tolerance - # Try to draw a source with the z_max based on the lens redshift and - # source number choice. - source_number = random.choices(self._choice, weights=self._weights)[ - 0 - ] - source_list = [] - for _ in range(source_number): - source = self._sources.draw_source(z_max=z_max) - source_list.append( - Source( - source_dict=source, - cosmo=self.cosmo, - source_type=self._sources.source_type, - light_profile=self._sources.light_profile, - ) + # Sample a lens (deflector) + lens = self._lens_galaxies.draw_deflector() + _lens = Deflector( + deflector_type=self._lens_galaxies.deflector_profile, + deflector_dict=lens, + ) + tolerance = 0.002 + z_max = _lens.redshift + tolerance + # Try to draw a source with the z_max based on the lens redshift and + # source number choice. + source_number = random.choices(self._choice, weights=self._weights)[ + 0 + ] + source_list = [] + valid_sources = True + for _ in range(source_number): + source = self._sources.draw_source(z_max=z_max) + # If the source is None, mark sources as invalid and break to retry + if source is None: + valid_sources = False + break + source_list.append( + Source( + source_dict=source, + cosmo=self.cosmo, + source_type=self._sources.source_type, + light_profile=self._sources.light_profile, ) - if source_number == 1: - _source = source_list[0] - else: - _source = source_list - # Compute test area for false positive position. - # This area will be used to determine the position of false positive. - test_area = 3 * draw_test_area(deflector=lens) - - # Create a FalsePositive instance with the lens and source information - false_positive = FalsePositive( - deflector_class=_lens, - source_class=_source, - cosmo=self.cosmo, - test_area=test_area, ) + if not valid_sources: + continue + if source_number == 1: + _source = source_list[0] + else: + _source = source_list + # Compute test area for false positive position. + # This area will be used to determine the position of false positive. + test_area = 3 * draw_test_area(deflector=lens) - # Add the false positive to the population - false_positive_population.append(false_positive) - successful = True + # Create a FalsePositive instance with the lens and source information + false_positive = FalsePositive( + deflector_class=_lens, + source_class=_source, + cosmo=self.cosmo, + test_area=test_area, + ) - except ValueError as e: - # Handle the specific case where no sources are found for z_max - if str(e).startswith("No galaxies found"): - continue + # Add the false positive to the population + false_positive_population.append(false_positive) + successful = True if number == 1: return false_positive_population[0] else: From 73fd867d037566cfdf7fff948e25c6d50de9006f Mon Sep 17 00:00:00 2001 From: narayan Date: Tue, 15 Oct 2024 15:08:31 -0400 Subject: [PATCH 37/87] minor change --- slsim/FalsePositives/false_positive.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slsim/FalsePositives/false_positive.py b/slsim/FalsePositives/false_positive.py index 52233df8d..5979253cb 100644 --- a/slsim/FalsePositives/false_positive.py +++ b/slsim/FalsePositives/false_positive.py @@ -6,7 +6,7 @@ from slsim.lens import theta_e_when_source_infinity -class FalsePositive(Lens): +class FalsePositive(object): """Class to manage individual false positive.""" def __init__( From e909c7ffacae02a95f3b015bd433e41047a59828 Mon Sep 17 00:00:00 2001 From: narayan Date: Thu, 17 Oct 2024 12:21:05 -0400 Subject: [PATCH 38/87] minor change --- slsim/lens.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/slsim/lens.py b/slsim/lens.py index 432fe889c..bb1ecda3a 100644 --- a/slsim/lens.py +++ b/slsim/lens.py @@ -596,10 +596,17 @@ def lenstronomy_kwargs(self, band=None): lens_light_model_list, kwargs_lens_light, ) = self.deflector.light_model_lenstronomy(band=band) - + # list of kwargs_model = { "lens_light_model_list": lens_light_model_list, "lens_model_list": lens_mass_model_list, + # source_redshift_list: redshifts for each source_light component + # z_source_convention: source redshift for convention of lens parameters (make sure LensCosmo has the same one) + # cosmo: astropy cosmology + # point_source_redshift_list: + # for multi-plane lensing only: + # lens_redshift_list: + # multi_plane: True } sources, sources_kwargs = self.source_light_model_lenstronomy(band=band) From 8d27775a32f7ee3e01b851947c24ca9135db52af Mon Sep 17 00:00:00 2001 From: narayan Date: Fri, 25 Oct 2024 09:48:57 -0400 Subject: [PATCH 39/87] minor change --- notebooks/false_positive_generation.ipynb | 230 ------------- slsim/FalsePositives/false_positive.py | 315 ------------------ slsim/FalsePositives/false_positive_pop.py | 112 ------- .../test_false_positive.py | 158 --------- .../test_false_positive_pop.py | 59 ---- 5 files changed, 874 deletions(-) delete mode 100644 notebooks/false_positive_generation.ipynb delete mode 100644 slsim/FalsePositives/false_positive.py delete mode 100644 slsim/FalsePositives/false_positive_pop.py delete mode 100644 tests/test_FalsePositives/test_false_positive.py delete mode 100644 tests/test_FalsePositives/test_false_positive_pop.py diff --git a/notebooks/false_positive_generation.ipynb b/notebooks/false_positive_generation.ipynb deleted file mode 100644 index 6883fc2c9..000000000 --- a/notebooks/false_positive_generation.ipynb +++ /dev/null @@ -1,230 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 45, - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "from astropy.cosmology import FlatLambdaCDM\n", - "from astropy.units import Quantity\n", - "from slsim.FalsePositives.false_positive_pop import FalsePositivePop\n", - "import slsim.Pipelines as pipelines\n", - "import slsim.Sources as sources\n", - "import slsim.Deflectors as deflectors\n", - "from slsim.image_simulation import lens_image" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# False positive simulations\n", - "\n", - "This notebook walks through the basics of simulating a galaxy-galaxy strong lenses like false postive.\n", - "\n", - "The notebook goes in three steps:\n", - "\n", - "1. The populations of elliptical and blue galaxy is produced.\n", - "2. The given number of false positive is generated.\n", - "3. A random false positive is selected and visualized in an image.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "# define a cosmology\n", - "cosmo = FlatLambdaCDM(H0=70, Om0=0.3)\n", - "\n", - "# define a sky area\n", - "sky_area = Quantity(value=0.1, unit=\"deg2\")\n", - "\n", - "\n", - "# define limits in the intrinsic deflector and source population (in addition to the skypy config\n", - "# file)\n", - "kwargs_deflector_cut = {\"band\": \"g\", \"band_max\": 28, \"z_min\": 0.01, \"z_max\": 2.5}\n", - "kwargs_source_cut = {\"band\": \"g\", \"band_max\": 28, \"z_min\": 0.1, \"z_max\": 5.0}" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "# Generate galaxy population using skypy pipeline.\n", - "galaxy_simulation_pipeline = pipelines.SkyPyPipeline(\n", - " skypy_config=None, sky_area=sky_area, filters=None, cosmo=cosmo\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/narayankhadka/slsim/slsim/Deflectors/elliptical_lens_galaxies.py:39: UserWarning: Angular size is converted to arcsec because provided input_catalog_type is skypy. If this is not correct, please refer to the documentation of the class you are using\n", - " galaxy_list = param_util.catalog_with_angular_size_in_arcsec(\n" - ] - } - ], - "source": [ - "# Initiate deflector population class.\n", - "lens_galaxies = deflectors.EllipticalLensGalaxies(\n", - " galaxy_list=galaxy_simulation_pipeline.red_galaxies,\n", - " kwargs_cut=kwargs_deflector_cut,\n", - " kwargs_mass2light=0.1,\n", - " cosmo=cosmo,\n", - " sky_area=sky_area,\n", - ")\n", - "# Initialize source pospulation class\n", - "source_galaxies = sources.Galaxies(\n", - " galaxy_list=galaxy_simulation_pipeline.blue_galaxies,\n", - " kwargs_cut=kwargs_source_cut,\n", - " cosmo=cosmo,\n", - " sky_area=sky_area,\n", - " catalog_type=\"skypy\",\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [], - "source": [ - "# make galaxy-galaxy lens like false positive class.\n", - "fp_pop = FalsePositivePop(\n", - " elliptical_galaxy_population=lens_galaxies,\n", - " blue_galaxy_population=source_galaxies,\n", - " cosmo=cosmo,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Generate given number of false positive" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [], - "source": [ - "fp_class = fp_pop.draw_false_positive(number=100)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Simulate image in i-band" - ] - }, - { - "cell_type": "code", - "execution_count": 41, - "metadata": {}, - "outputs": [], - "source": [ - "# load your psf kernel and transform matrix. If you have your own psf, please provide\n", - "# it here.\n", - "path = \"../tests/TestData/psf_kernels_for_deflector.npy\"\n", - "psf_kernel = 1 * np.load(path)\n", - "psf_kernel[psf_kernel < 0] = 0\n", - "transform_matrix = np.array([[0.2, 0], [0, 0.2]])" - ] - }, - { - "cell_type": "code", - "execution_count": 43, - "metadata": {}, - "outputs": [], - "source": [ - "image_false_positive_i = lens_image(\n", - " lens_class=fp_class[18],\n", - " band=\"i\",\n", - " mag_zero_point=31,\n", - " num_pix=64,\n", - " psf_kernel=psf_kernel,\n", - " transform_pix2angle=transform_matrix,\n", - " exposure_time=30,\n", - " with_deflector=True,\n", - " with_source=False,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 44, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 44, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAPsAAAD4CAYAAAAq5pAIAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/TGe4hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAU2klEQVR4nO3dbYwd1X3H8e/Pu2svNhDzFMvBJNCWBqG2QLQlQYmiBpKUplHgRYTyoNaqLPlNWhElUgqtVClSVSVv8vCiInJDEr9IA5Q8gFCahDhEVarKiQkkAZwUh4KwYzAEHIwxftp/X9yx75mze2dn794Hr8/vI6127p25M/+9d/93/mfOzBlFBGZ2+lsx7gDMbDSc7GaFcLKbFcLJblYIJ7tZISZHubGVWhXTrBnlJs2K8ioHORKHNd+8kSb7NGt4s64b5SbNirI9tvWc5zLerBBOdrNCONnNCuFkNyuEk92sEE52s0I42c0K0SrZJa2VdLekX0raKekaSedKul/S49Xvc4YdrJn1r+2e/fPAdyLiMuAKYCdwC7AtIi4FtlWPzewUtWCyS3oN8HbgdoCIOBIR+4EbgK3VYluBG4cTopkNQps9+yXAc8CXJT0k6YuS1gDrImJvtcwzwLr5Xixps6QdknYc5fBgojazRWuT7JPAm4DbIuIq4CBZyR6dsa3mHd8qIrZExExEzEyxaqnxmlmf2iT7bmB3RGyvHt9NJ/mflbQeoPq9bzghmtkgLJjsEfEM8LSkN1ZPXQc8BtwLbKye2wjcM5QIzWwg2l7i+nfAVyWtBJ4A/obOF8VdkjYBTwE3DSdEMxuEVskeEQ8DM/PM8sXpZsuEz6AzK4ST3awQTnazQjjZzQrhZDcrhJPdrBBOdrNCONnNCuFkNyuEk92sEE52s0I42c0K4WQ3K4ST3awQTnazQjjZzQrhZDcrhJPdrBBOdrNCONnNCuFkNyuEk92sEE52s0I42c0K4WQ3K0SrO8JIehI4ABwHjkXEjKRzgTuBi4EngZsi4sXhhGlmS7WYPfs7IuLKiDhxG6hbgG0RcSmwjew2zmZ2allKGX8DsLWa3grcuORozGxo2iZ7AN+T9KCkzdVz6yJibzX9DLBuvhdK2ixph6QdRzm8xHDNrF9tb9n8tojYI+m1wP2SfpnOjIiQFPO9MCK2AFsAzta58y5jZsPXas8eEXuq3/uAbwJXA89KWg9Q/d43rCDNbOkWTHZJaySddWIaeDfwCHAvsLFabCNwz7CCNLOla1PGrwO+KenE8v8eEd+R9BPgLkmbgKeAm4YXppkt1YLJHhFPAFfM8/xvgeuGEZQtQudLeH7hQyTW5TPozArhZDcrhJPdrBBt+9ltnBbTLu+1bClt+xUT9cezx7vT6XtwOv3NLXnPblYIJ7tZIVzGj1pTOd3zNdl3csz2Xl9t2VmK0PSepvPS9yZ/SVrun6a8ZzcrhJPdrBAu44etscRckT1sW+J3jzjH8Xr5ma4jZpP1R0NJnx/Bblq2ttyYjmg3Nl2yWRMTrZar/SVNf/8yPorvPbtZIZzsZoVwspsVwm32YWjZTm9qo2uy+9FE1k5Uuv6J/Iyx7rJKZ0V9W5EsN6eNqpZt/VGeldeyy1L5+5G8x+m8ucc6kvf76LH6Ok6Tbjnv2c0K4WQ3K4TL+H61vKhiTlmZzpvM3v5kWa1c2Z3OX5iU1vm8OJ6U3UmpOqdsTccHbSrxW5f0+X4jWTZ9f/JyvKnc71W6582ayeR9m6q/p1o5Nf86jmefS/r+ZO9VJF2dy7mk957drBBOdrNCONnNCuE2+yAs5vTNpJ2etssBtGZ198H0qpOT0avdCXPbr787kITVjWv24Cv1lx050n0wm8fbo13a8He1PsW2SVN7Pp23mNN70+MPSXte2d8Ss+mVhPlVhkd7r38Z8Z7drBBOdrNCuIwfAE32LrOVlOOdZSd7zouzzzw5fez8ZHpN/WM6vrL7HT15qF5yT5zXfd3kCwdPTq/ISuQ4mFwdl5b0DfLuu9aauinbzqsFkpftaddYQ0lf64pcRLOjaUCQZXQVnPfsZoVoneySJiQ9JOm+6vElkrZL2iXpTkkrF1qHmY3PYsr4m4GdwNnV408Dn42IOyR9AdgE3Dbg+JaFvLxd0XD0PC3dj7/uvNq8I+edcXL6pTd013Hogno5e2Rtt3ScOlD/CM96svude/aT3e/yqfyim2PdmOcUy0lZH8e6F4U0XbgTc47o12Z2p9uOF9ckK53TM/7iSP3IeS2qFQ0xJuV/Y3NlGZXtuVZ7dkkbgL8Evlg9FnAtcHe1yFbgxiHEZ2YD0raM/xzwCbpHJ84D9kfEia/93cCF871Q0mZJOyTtOMrhpcRqZkvQ5v7s7wX2RcSD/WwgIrZExExEzEyxauEXmNlQtGmzvxV4n6T3ANN02uyfB9ZKmqz27huAPcMLc0z6GeOd+mATeZcXZ0yfnDy+un5M8+XXddvpL/xxtw35h3+0u7bca8/oniW379BZtXm7HrqoG8dEd1uvyWKcStrs7M9mJu302uAY+dVgsy3br22vnGvb9Tbndb3PfptN2vC1KxAb41i+3WtNFtyzR8StEbEhIi4GPgD8ICI+DDwAvL9abCNwz9CiNLMlW0o/+98DH5O0i04b/vbBhGRmw7CoM+gi4ofAD6vpJ4CrBx/SKaRlWZl3SdXKxWwwhZjulu6zK+vftWkX25mvf+nk9Idet7223F+f/fzJ6a+/fHZt3r8cvP7k9MsvnH9yevVz9TgmX+zGsWIi+85PB9VIu6SygThqY9w1dFf1HAyjM7M73XQH1tqGG8r7/DXJsnGs6WKXlmPtLeOS3mfQmRXCyW5WCCe7WSF81Vu/mu4blow7rtVZe/5Q98SiUL3bbPJQd/rgq91uuDUrel+V9qfTv6k9Pnu6u/6Dq7rty9mprJ07kbRl83ZoegxiqhuH8q639Gq5fGDNZNn6/efy4yDJ+9jQ3q5vuOFYStsBMJos43Z5E+/ZzQrhZDcrhMv4xaiVd2lXTe+x4cmuwuLQqycnV/721dqs6Re6JfOB3d3x6D591p/Xltv1+odPTv/3C79fm/fUnqS7LRmgYsXhhrHts7Hwale6pWV3Ps59+pp8AIyezZysVG97RVw/5Xi+bLqOWL7jv/fLe3azQjjZzQrhMr5Jy1sV5UeYtaJhIISkRJ743cHarDOfTs5qO9a9QvB3z19QW+7fznvnyenJV+oxnrW/O73mN904pg7UmxN6tffwyDqjO4hGpD0B2dHytsV0+h4oa/K0PqttlHeMPU15z25WCCe7WSGc7GaFcJu9Sdu2YHbFVG3wxbzrLR308KUDtVlTq7pdb2e93G0rr9p/Rm25Y6u761B2SGAiGUd+1fPdU/J0NFswHUN9OrsNVXoGoJLbUOXda+mxinTAi1zTIBf9DmxRW8cArko7Ta5sa+I9u1khnOxmhXAZ36/GsjKZzsdaP5qU9fmgEc+90J2VXIAyfXBNtv6GbqjJbtdWetFN4+ASq6frj5N1pM0OHcr+XaaSvyVvyuTNlx7LNY0f13PwijnrHEDZfZqW7inv2c0K4WQ3K4ST3awQbrMPwpzTapPTZY9mXVLpQA4H6+1XJfeIi+TqOL2a3Uknbevn7dx0/em68/ucJfeci6n6Kayz08mAFUm32Ypj2emy6XGA/Iq4/O8+sa2mwStybbvDCug2GwTv2c0K4WQ3K4TL+EFoGhMt75JKKl9N1r9re3ZX5d1maUmej+WebjudtyoboCJpCsTK+r/B7Mrkdcn6VhzK4kvL+obyWXkXYxpH+re1HYNuzkpcurfhPbtZIdrcxXVa0o8l/UzSo5I+WT1/iaTtknZJulPSyoXWZWbj06aMPwxcGxEvS5oCfiTpP4GPAZ+NiDskfQHYBNw2xFjHr+3Qxi3HS8sHtlA+HPPJ5bKzzpLHms2aCck60i3nw0XrcLckX3EwO9rfy2LK5fRCm9rYb01n0DWU7T7ivmRt7uIaEfFy9XCq+gngWuDu6vmtwI3DCNDMBqNVm13ShKSHgX3A/cCvgf3VvdkBdgMX9njtZkk7JO04Sss9iJkNXKtkj4jjEXElsIHOnVsva7uBiNgSETMRMTPFqoVfYGZDsdhbNu+X9ABwDbBW0mS1d98A7BlGgKeUftqK/XbL5VfL1daRdJvlbfHkcTpPebdecsabXqmPX1/bA6THB/Kz4tLjBU3dg/mgF7WABzB4hbXS5mj8BZLWVtNnAO8CdgIPAO+vFtsI3DOkGM1sANrs2dcDWyVN0PlyuCsi7pP0GHCHpH8GHgJuH2KcZrZECyZ7RPwcuGqe55+g0363Jou5VVHMf0uppnHp59xNKV2ux/MAvJKMTzedHUtJLrxJL6BpGoMusot1at2FabMjL/ebSvdeWo7nb3U+g86sEE52s0I42c0K4avehq3toAvQs23b6zTaeaXt6OPdNraywSVqUdV73mqDY8Th3idCpe30vAuwZ1s8f75t15stmffsZoVwspsVwmX8OM0p8Xt0V+XjtjWUu0or/qbx3dKr0vLusF5XmM323m4+1l56BmAt/qaYms42bFrOWvGe3awQTnazQriMP1W1PDKdH6lPS+a0pI/8LquLOcLfS9pjkF24Uyvd+z3K7nJ9oLxnNyuEk92sEE52s0K4zb7MzemWS+c1tKmbbuHca/2N7fK5L+zxfMvutXye2+9L5j27WSGc7GaFcBl/KmlbqjaVvuktlJSexdbf93rPM+E6TzS9cP7lFjWYh0v3QfKe3awQTnazQjjZzQrhNvty0NTOnTMYRK8rxfo7ZTXa9dC1357b4WPjPbtZIZzsZoVwGX+qanv22GK6snrJ178iuSKuR1feguvoxWO+j4337GaFaHOvt4skPSDpMUmPSrq5ev5cSfdLerz6fc7wwzWzfrXZsx8DPh4RlwNvAT4i6XLgFmBbRFwKbKse26BEdH8GsY78JyXVf2K2+5M+3yRfR6/XNcVhQ7VgskfE3oj4aTV9gM4dXC8EbgC2VottBW4cUoxmNgCLOkAn6WI6N3ncDqyLiL3VrGeAdT1esxnYDDDN6r4DNbOlaX2ATtKZwNeBj0bES+m86NwOZN6aLCK2RMRMRMxMsWq+RcxsBFrt2SVN0Un0r0bEN6qnn5W0PiL2SloP7BtWkMVbTHdVOq9tV1nb7rtBtLHd9TY2bY7GC7gd2BkRn0lm3QtsrKY3AvcMPjwzG5Q2e/a3An8F/ELSw9Vz/wB8CrhL0ibgKeCmoURoZgOxYLJHxI+AXnXedYMNx+bVtmxfzLxRnqGXctk+Nj6DzqwQTnazQjjZzQrhq95K1bbt7Db2acN7drNCONnNCuEy3vrn2zMtK96zmxXCyW5WCCe7WSHcZrf+uZ2+rHjPblYIJ7tZIVzGLwce8MEGwHt2s0I42c0K4TJ+OXDZbgPgPbtZIZzsZoVwspsVwsluVggnu1khnOxmhXCymxXCyW5WiDb3evuSpH2SHkmeO1fS/ZIer36fM9wwzWyp2uzZvwJcnz13C7AtIi4FtlWPzewUtmCyR8R/AS9kT98AbK2mtwI3DjYsMxu0fs+NXxcRe6vpZ4B1vRaUtBnYDDDN6j43Z2ZLteQDdBERQM8rNSJiS0TMRMTMFKuWujkz61O/yf6spPUA1e99gwvJzIah32S/F9hYTW8E7hlMOGY2LG263r4G/A/wRkm7JW0CPgW8S9LjwDurx2Z2ClvwAF1EfLDHrOsGHIuZDZHPoDMrhJPdrBBOdrNCONnNCuFkNyuEk92sEE52s0I42c0K4WQ3K4ST3awQTnazQjjZzQrhZDcrhJPdrBBOdrNCONnNCuFkNyuEk92sEE52s0I42c0K4WQ3K4ST3awQTnazQjjZzQrhZDcrxJKSXdL1kn4laZekWwYVlJkNXt/JLmkC+FfgL4DLgQ9KunxQgZnZYC1lz341sCsinoiII8AdwA2DCcvMBm0pyX4h8HTyeHf1XI2kzZJ2SNpxlMNL2JyZLcXQD9BFxJaImImImSlWDXtzZtbDgrdsbrAHuCh5vKF6rqcDvPj89+Pup4DzgeeXsO1BOBViAMeRcxx1i43jDb1mKCL6ikDSJPC/dO7Tvgf4CfChiHi0xWt3RMRMXxsekFMhBsfhOEYZR9979og4Julvge8CE8CX2iS6mY3HUsp4IuLbwLcHFIuZDdG4zqDbMqbtpk6FGMBx5BxH3cDi6LvNbmbLi8+NNyuEk92sECNN9nFdOCPpS5L2SXokee5cSfdLerz6fc4I4rhI0gOSHpP0qKSbxxGLpGlJP5b0syqOT1bPXyJpe/X53Clp5TDjSOKZkPSQpPvGFYekJyX9QtLDknZUz43jf2StpLsl/VLSTknXDCqOkSX7mC+c+QpwffbcLcC2iLgU2FY9HrZjwMcj4nLgLcBHqvdg1LEcBq6NiCuAK4HrJb0F+DTw2Yj4A+BFYNOQ4zjhZmBn8nhccbwjIq5M+rXH8T/yeeA7EXEZcAWd92UwcUTESH6Aa4DvJo9vBW4d4fYvBh5JHv8KWF9Nrwd+NapYkhjuAd41zliA1cBPgTfTOVNrcr7Pa4jb31D9A18L3AdoTHE8CZyfPTfSzwV4DfB/VAfOBx3HKMv4VhfOjNC6iNhbTT8DrBvlxiVdDFwFbB9HLFXp/DCwD7gf+DWwPyKOVYuM6vP5HPAJYLZ6fN6Y4gjge5IelLS5em7Un8slwHPAl6tmzRclrRlUHD5AB0TnK3NkfZCSzgS+Dnw0Il4aRywRcTwirqSzZ70auGzY28xJei+wLyIeHPW25/G2iHgTnWbmRyS9PZ05os9lEngTcFtEXAUcJCvZlxLHKJN90RfODNmzktYDVL/3jWKjkqboJPpXI+Ib44wFICL2Aw/QKZfXVtc8wGg+n7cC75P0JJ3xEK6l02YddRxExJ7q9z7gm3S+AEf9uewGdkfE9urx3XSSfyBxjDLZfwJcWh1pXQl8ALh3hNvP3QtsrKY30mk/D5UkAbcDOyPiM+OKRdIFktZW02fQOW6wk07Sv39UcUTErRGxISIupvP/8IOI+PCo45C0RtJZJ6aBdwOPMOLPJSKeAZ6W9MbqqeuAxwYWx7APfGQHGt5D50q5XwP/OMLtfg3YCxyl8+25iU7bcBvwOPB94NwRxPE2OiXYz4GHq5/3jDoW4E+Ah6o4HgH+qXr+94AfA7uA/wBWjfAz+jPgvnHEUW3vZ9XPoyf+N8f0P3IlsKP6bL4FnDOoOHy6rFkhfIDOrBBOdrNCONnNCuFkNyuEk92sEE52s0I42c0K8f9GUlGPl1s/vwAAAABJRU5ErkJggg==", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "plt.imshow(image_false_positive_i, origin=\"lower\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "base", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/slsim/FalsePositives/false_positive.py b/slsim/FalsePositives/false_positive.py deleted file mode 100644 index 5979253cb..000000000 --- a/slsim/FalsePositives/false_positive.py +++ /dev/null @@ -1,315 +0,0 @@ -import numpy as np -from lenstronomy.Cosmo.lens_cosmo import LensCosmo -from slsim.ParamDistributions.los_config import LOSConfig -from slsim.lens import Lens -from slsim.Util.param_util import ellipticity_slsim_to_lenstronomy -from slsim.lens import theta_e_when_source_infinity - - -class FalsePositive(object): - """Class to manage individual false positive.""" - - def __init__( - self, - source_class, - deflector_class, - cosmo, - test_area=4 * np.pi, - los_config=None, - los_dict=None, - ): - """ - :param source_class: A Source class instance or list of Source class instance - :type source_class: Source class instance from slsim.Sources.source - :param deflector_class: deflector instance - :type deflector_class: Deflector class instance from slsim.Deflectors.deflector - :param cosmo: astropy.cosmology instance - :param test_area: area of disk around one lensing galaxies to be investigated - on (in arc-seconds^2). - :param los_config: LOSConfig instance which manages line-of-sight (LOS) effects - and Gaussian mixture models in a simulation or analysis context. - :param los_dict: line of sight dictionary (optional, takes these values instead - of drawing from distribution) Takes "gamma" = [gamma1, gamma2] and - "kappa" = kappa as entries - :type los_dict: dict - """ - self.deflector = deflector_class - self.source = source_class - self.test_area = test_area - self.cosmo = cosmo - if isinstance(self.source, list): - source_z = self.source[0].redshift - self._source_type = self.source[0].source_type - self.source_number = len(self.source) - self.single_source_class = self.source[0] # to access some common kwargs. - else: - source_z = self.source.redshift - self._source_type = self.source.source_type - self.source_number = 1 - self.single_source_class = self.source - self._lens_cosmo = LensCosmo( - z_lens=float(self.deflector.redshift), - z_source=float(source_z), - cosmo=self.cosmo, - ) - - self._los_linear_distortions_cache = None - self.los_config = los_config - if self.los_config is None: - if los_dict is None: - los_dict = {} - self.los_config = LOSConfig(**los_dict) - - @property - def deflector_position(self): - """Center of the deflector position. - - :return: [x_pox, y_pos] in arc seconds - """ - return self.deflector.deflector_center - - @property - def deflector_redshift(self): - """ - - :return: lens redshift - """ - return self.deflector.redshift - - @property - def source_redshift(self): - """ - - :return: a source redshift or list of source redshift - """ - if self.source_number == 1: - source_redshift = self.source.redshift - else: - source_redshift = [] - for i in range(self.source_number): - source_redshift.append(self.source[i].redshift) - return source_redshift - - @property - def external_convergence(self): - """ - - :return: external convergence - """ - _, _, kappa_ext = self.los_linear_distortions - return kappa_ext - - @property - def external_shear(self): - """ - - :return: the absolute external shear - """ - gamma1, gamma2, _ = self.los_linear_distortions - return (gamma1**2 + gamma2**2) ** 0.5 - - @property - def einstein_radius(self): - """Einstein radius, from SIS approximation (coming from velocity dispersion) + - external convergence effect. - - :return: Einstein radius [arc seconds] - """ - theta_E = theta_e_when_source_infinity( - v_sigma=self.deflector_velocity_dispersion() - ) - _, _, kappa_ext = self.los_linear_distortions - return theta_E / (1 - kappa_ext) - - def deflector_ellipticity(self): - """ - - :return: e1_light, e2_light, e1_mass, e2_mass - """ - e1_light, e2_light = self.deflector.light_ellipticity - e1_mass, e2_mass = self.deflector.mass_ellipticity - return e1_light, e2_light, e1_mass, e2_mass - - def deflector_stellar_mass(self): - """ - - :return: stellar mass of deflector - """ - return self.deflector.stellar_mass - - def deflector_velocity_dispersion(self): - """ - - :return: velocity dispersion [km/s] - """ - return self.deflector.velocity_dispersion(cosmo=self.cosmo) - - @property - def los_linear_distortions(self): - if self._los_linear_distortions_cache is None: - self._los_linear_distortions_cache = ( - self._calculate_los_linear_distortions() - ) - return self._los_linear_distortions_cache - - def _calculate_los_linear_distortions(self): - """Line-of-sight distortions in shear and convergence. - - :return: kappa, gamma1, gamma2 - """ - if self.source_number == 1: - source_z = self.source_redshift - else: - source_z = self.source_redshift[0] - return self.los_config.calculate_los_linear_distortions( - source_redshift=source_z, - deflector_redshift=self.deflector_redshift, - ) - - def deflector_magnitude(self, band): - """Apparent magnitude of the deflector for a given band. - - :param band: imaging band - :type band: string - :return: magnitude of deflector in given band - """ - return self.deflector.magnitude(band=band) - - def extended_source_magnitude(self, band): - """Unlensed apparent magnitude of the extended source for a given band (assumes - that size is the same for different bands) - - :param band: imaging band - :type band: string - :param lensed: if True, returns the lensed magnified magnitude - :type lensed: bool - :return: magnitude of source in given band - """ - # band_string = str("mag_" + band) - # TODO: might have to change conventions between extended and point source - source_mag = self.source.extended_source_magnitude(band) - return source_mag - - def lenstronomy_kwargs(self, band=None): - """Generates lenstronomy dictionary conventions for the class object. - - :param band: imaging band, if =None, will result in un-normalized amplitudes - :type band: string or None - :return: lenstronomy model and parameter conventions - """ - lens_mass_model_list, kwargs_lens = self.deflector_mass_model_lenstronomy() - ( - lens_light_model_list, - kwargs_lens_light, - ) = self.deflector.light_model_lenstronomy(band=band) - - sources, sources_kwargs = self.source_light_model_lenstronomy(band=band) - combined_lens_light_model_list = ( - lens_light_model_list + sources["source_light_model_list"] - ) - combined_kwargs_lens_light = kwargs_lens_light + sources_kwargs["kwargs_source"] - - kwargs_model = { - "lens_light_model_list": combined_lens_light_model_list, - "lens_model_list": lens_mass_model_list, - } - - kwargs_source = None - kwargs_ps = sources_kwargs["kwargs_ps"] - - kwargs_params = { - "kwargs_lens": kwargs_lens, - "kwargs_source": kwargs_source, - "kwargs_lens_light": combined_kwargs_lens_light, - "kwargs_ps": kwargs_ps, - } - - return kwargs_model, kwargs_params - - def deflector_mass_model_lenstronomy(self): - """Returns lens model instance and parameters in lenstronomy conventions. - - :return: lens_model_list, kwargs_lens - """ - if self.deflector.deflector_type in ["EPL", "NFW_HERNQUIST", "NFW_CLUSTER"]: - lens_mass_model_list, kwargs_lens = self.deflector.mass_model_lenstronomy( - lens_cosmo=self._lens_cosmo - ) - else: - raise ValueError( - "Deflector model %s not supported for lenstronomy model" - % self.deflector.deflector_type - ) - # adding line-of-sight structure - gamma1, gamma2, kappa_ext = self.los_linear_distortions - gamma1_lenstronomy, gamma2_lenstronomy = ellipticity_slsim_to_lenstronomy( - e1_slsim=gamma1, e2_slsim=gamma2 - ) - kwargs_lens.append( - { - "gamma1": gamma1_lenstronomy, - "gamma2": gamma2_lenstronomy, - "ra_0": 0, - "dec_0": 0, - } - ) - kwargs_lens.append({"kappa": kappa_ext, "ra_0": 0, "dec_0": 0}) - lens_mass_model_list.append("SHEAR") - lens_mass_model_list.append("CONVERGENCE") - - return lens_mass_model_list, kwargs_lens - - def deflector_light_model_lenstronomy(self, band): - """Returns lens model instance and parameters in lenstronomy conventions. - - :param band: imaging band - :type band: str - :return: lens_light_model_list, kwargs_lens_light - """ - return self.deflector.light_model_lenstronomy(band=band) - - def source_light_model_lenstronomy(self, band=None): - """Returns source light model instance and parameters in lenstronomy - conventions. - - :return: source_light_model_list, kwargs_source_light - """ - source_models = {} - all_source_kwarg_dict = {} - """If ( self._source_type == "extended". - - or self._source_type == "point_plus_extended" ): - """ - if self.source_number == 1: - source_class = self.source - else: - source_class = self.source[0] - if source_class.light_profile == "single_sersic": - source_models["source_light_model_list"] = [ - "SERSIC_ELLIPSE" - ] * self.source_number - # In this case we will consider a single source with double sersic profile. - else: - raise ValueError( - "Provided light profile is not supported. Supported" - " light profile is single_sersic" - ) - if self.source_number == 1: - kwargs_source = self.source.kwargs_extended_source_light( - draw_area=self.test_area, center_lens=self.deflector_position, band=band - ) - else: - kwargs_source = [] - for i in range(self.source_number): - kwargs_source.append( - self.source[i].kwargs_extended_source_light( - draw_area=self.test_area, - center_lens=self.deflector_position, - band=band, - )[0] - ) - - kwargs_ps = None - all_source_kwarg_dict["kwargs_source"] = kwargs_source - all_source_kwarg_dict["kwargs_ps"] = kwargs_ps - return source_models, all_source_kwarg_dict diff --git a/slsim/FalsePositives/false_positive_pop.py b/slsim/FalsePositives/false_positive_pop.py deleted file mode 100644 index 56464e5d4..000000000 --- a/slsim/FalsePositives/false_positive_pop.py +++ /dev/null @@ -1,112 +0,0 @@ -from slsim.FalsePositives.false_positive import FalsePositive -from typing import Optional -from astropy.cosmology import Cosmology -from slsim.Sources.source_pop_base import SourcePopBase -from slsim.ParamDistributions.los_config import LOSConfig -from slsim.Deflectors.deflectors_base import DeflectorsBase -from slsim.Sources.source import Source -from slsim.Deflectors.deflector import Deflector -from slsim.lens_pop import draw_test_area -import random - - -class FalsePositivePop(object): - """Class to perform samples of false positive population.""" - - def __init__( - self, - elliptical_galaxy_population: DeflectorsBase, - blue_galaxy_population: SourcePopBase, - cosmo: Optional[Cosmology] = None, - los_config: Optional[LOSConfig] = None, - source_number_choice: Optional[list[int]] = [1, 2, 3], - weights_for_source_number: Optional[list[int]] = None, - ): - """ - Args: - deflector_population (DeflectorsBase): Deflector population as an instance of a DeflectorsBase subclass. - source_population (SourcePopBase): Source population as an instance of a SourcePopBase subclass - cosmo (Optional[Cosmology], optional): AstroPy Cosmology instance. If None, defaults to flat LCDM with h0=0.7 and Om0=0.3. - Defaults to None. - los_config (Optional[LOSConfig], optional): Configuration for line of sight distribution. Defaults to None. - source_number_choice (Optional[list[int]], optional): A list of integers to choose source number from. If None, defaults to [1, 2, 3]. - weights (Optional[list[int]], optional): A list of weights corresponding to the probabilities of selecting each value - in source_number_choice. If None, all choices are equally likely. - Defaults to None. - """ - - self.cosmo = cosmo - self._lens_galaxies = elliptical_galaxy_population - self._sources = blue_galaxy_population - self._choice = source_number_choice - self._weights = weights_for_source_number - self.los_config = los_config - if self.los_config is None: - self.los_config = LOSConfig() - - def draw_false_positive(self, number=1): - """Draw given number of false positives within the cuts of the lens and source. - - :param number: number of false positive requested. The default value is 1. - :return: list of FalsePositive() instance with parameters of a pair of - elliptical and blue galaxy. - """ - false_positive_population = [] - - for _ in range(number): - successful = False - while not successful: - # Sample a lens (deflector) - lens = self._lens_galaxies.draw_deflector() - _lens = Deflector( - deflector_type=self._lens_galaxies.deflector_profile, - deflector_dict=lens, - ) - tolerance = 0.002 - z_max = _lens.redshift + tolerance - # Try to draw a source with the z_max based on the lens redshift and - # source number choice. - source_number = random.choices(self._choice, weights=self._weights)[ - 0 - ] - source_list = [] - valid_sources = True - for _ in range(source_number): - source = self._sources.draw_source(z_max=z_max) - # If the source is None, mark sources as invalid and break to retry - if source is None: - valid_sources = False - break - source_list.append( - Source( - source_dict=source, - cosmo=self.cosmo, - source_type=self._sources.source_type, - light_profile=self._sources.light_profile, - ) - ) - if not valid_sources: - continue - if source_number == 1: - _source = source_list[0] - else: - _source = source_list - # Compute test area for false positive position. - # This area will be used to determine the position of false positive. - test_area = 3 * draw_test_area(deflector=lens) - - # Create a FalsePositive instance with the lens and source information - false_positive = FalsePositive( - deflector_class=_lens, - source_class=_source, - cosmo=self.cosmo, - test_area=test_area, - ) - - # Add the false positive to the population - false_positive_population.append(false_positive) - successful = True - if number == 1: - return false_positive_population[0] - else: - return false_positive_population diff --git a/tests/test_FalsePositives/test_false_positive.py b/tests/test_FalsePositives/test_false_positive.py deleted file mode 100644 index 9ed617f67..000000000 --- a/tests/test_FalsePositives/test_false_positive.py +++ /dev/null @@ -1,158 +0,0 @@ -import pytest -import numpy as np -from astropy.cosmology import FlatLambdaCDM -from slsim.ParamDistributions.los_config import LOSConfig -from slsim.Sources.source import Source -from slsim.Deflectors.deflector import Deflector -import slsim.Sources as sources -import slsim.Deflectors as deflectors -import slsim.Pipelines as pipelines -from slsim.FalsePositives.false_positive import FalsePositive -from astropy.units import Quantity - -sky_area = Quantity(value=0.01, unit="deg2") -galaxy_simulation_pipeline = pipelines.SkyPyPipeline( - skypy_config=None, - sky_area=sky_area, - filters=None, -) -kwargs_deflector_cut = {"band": "g", "band_max": 28, "z_min": 0.01, "z_max": 2.5} -kwargs_source_cut = {"band": "g", "band_max": 28, "z_min": 0.1, "z_max": 5.0} - - -def test_false_positive(): - # Mock objects for source_class and deflector_class - - # Initialize a cosmology instance - cosmo = FlatLambdaCDM(H0=70, Om0=0.3) - lens_galaxies = deflectors.EllipticalLensGalaxies( - galaxy_list=galaxy_simulation_pipeline.red_galaxies, - kwargs_cut=kwargs_deflector_cut, - kwargs_mass2light=0.1, - cosmo=cosmo, - sky_area=sky_area, - ) - source_galaxies = sources.Galaxies( - galaxy_list=galaxy_simulation_pipeline.blue_galaxies, - kwargs_cut=kwargs_source_cut, - cosmo=cosmo, - sky_area=sky_area, - catalog_type="skypy", - ) - single_deflector = lens_galaxies.draw_deflector() - single_source1 = source_galaxies.draw_source() - single_source2 = source_galaxies.draw_source() - lens = Deflector(deflector_type="EPL", deflector_dict=single_deflector) - source = Source( - source_dict=single_source1, - cosmo=cosmo, - source_type="extended", - light_profile="single_sersic", - ) - source2 = Source( - source_dict=single_source1, - cosmo=cosmo, - source_type="extended", - light_profile="double_sersic", - ) - source_list = [ - Source( - source_dict=single_source1, - cosmo=cosmo, - source_type="extended", - light_profile="single_sersic", - ), - Source( - source_dict=single_source2, - cosmo=cosmo, - source_type="extended", - light_profile="single_sersic", - ), - ] - # LOS configuration - los_config = LOSConfig() - - # Create an instance of FalsePositive - false_positive_instance_1 = FalsePositive( - source_class=source, - deflector_class=lens, - cosmo=cosmo, - test_area=4 * np.pi, - ) - false_positive_instance_2 = FalsePositive( - source_class=source_list, - deflector_class=lens, - cosmo=cosmo, - test_area=4 * np.pi, - los_config=los_config, - ) - false_positive_instance_3 = FalsePositive( - source_class=source2, - deflector_class=lens, - cosmo=cosmo, - test_area=4 * np.pi, - los_config=los_config, - ) - required_keys = { - "magnitude", - "R_sersic", - "n_sersic", - "e1", - "e2", - "center_x", - "center_y", - } - assert false_positive_instance_1.source_number == 1 - assert false_positive_instance_2.source_number == 2 - assert ( - false_positive_instance_1.lenstronomy_kwargs("i")[0]["lens_light_model_list"][0] - == "SERSIC_ELLIPSE" - ) - assert ( - len( - false_positive_instance_2.lenstronomy_kwargs("i")[0][ - "lens_light_model_list" - ] - ) - == 3 - ) - assert ( - len(false_positive_instance_2.lenstronomy_kwargs("i")[1]["kwargs_lens_light"]) - == 3 - ) - assert len(false_positive_instance_2.deflector_position) == 2 - assert false_positive_instance_2.deflector_redshift == single_deflector["z"] - assert false_positive_instance_1.source_redshift == single_source1["z"] - assert np.all(false_positive_instance_2.source_redshift) == np.all( - np.array([single_source1["z"], single_source2["z"]]) - ) - assert false_positive_instance_1.external_convergence < 0.1 - assert false_positive_instance_1.external_shear < 0.2 - assert false_positive_instance_1.einstein_radius < 2.5 - assert ( - false_positive_instance_1.deflector_magnitude(band="i") - == single_deflector["mag_i"] - ) - assert ( - false_positive_instance_1.extended_source_magnitude(band="i") - == single_source1["mag_i"] - ) - assert len(false_positive_instance_1.deflector_ellipticity()) == 4 - assert ( - false_positive_instance_1.deflector_stellar_mass() - == single_deflector["stellar_mass"] - ) - assert ( - set( - false_positive_instance_1.deflector_light_model_lenstronomy(band="i")[1][ - 0 - ].keys() - ) - == required_keys - ) - with pytest.raises(ValueError): - false_positive_instance_3.source_light_model_lenstronomy(band="i") - - -if __name__ == "__main__": - pytest.main() diff --git a/tests/test_FalsePositives/test_false_positive_pop.py b/tests/test_FalsePositives/test_false_positive_pop.py deleted file mode 100644 index cbd9b419c..000000000 --- a/tests/test_FalsePositives/test_false_positive_pop.py +++ /dev/null @@ -1,59 +0,0 @@ -import pytest -from astropy.cosmology import FlatLambdaCDM -import slsim.Sources as sources -import slsim.Deflectors as deflectors -import slsim.Pipelines as pipelines -from slsim.FalsePositives.false_positive_pop import FalsePositivePop -from astropy.units import Quantity - -sky_area = Quantity(value=0.01, unit="deg2") -cosmo = FlatLambdaCDM(H0=70, Om0=0.3) -galaxy_simulation_pipeline = pipelines.SkyPyPipeline( - skypy_config=None, - sky_area=sky_area, - filters=None, -) -kwargs_deflector_cut = {"band": "g", "band_max": 28, "z_min": 0.01, "z_max": 2.5} -kwargs_source_cut = {"band": "g", "band_max": 28, "z_min": 0.1, "z_max": 5.0} -red_galaxy_list = galaxy_simulation_pipeline.red_galaxies -blue_galaxy_list = galaxy_simulation_pipeline.blue_galaxies -lens_galaxies = deflectors.EllipticalLensGalaxies( - galaxy_list=red_galaxy_list, - kwargs_cut=kwargs_deflector_cut, - kwargs_mass2light=0.1, - cosmo=cosmo, - sky_area=sky_area, -) -source_galaxies = sources.Galaxies( - galaxy_list=blue_galaxy_list, - kwargs_cut=kwargs_source_cut, - cosmo=cosmo, - sky_area=sky_area, - catalog_type="skypy", -) - - -def test_draw_false_positive_single(): - fp_pop1 = FalsePositivePop( - elliptical_galaxy_population=lens_galaxies, - blue_galaxy_population=source_galaxies, - cosmo=cosmo, - source_number_choice=[1], - ) - draw_fp1 = fp_pop1.draw_false_positive() - assert isinstance(draw_fp1, object) - - -def test_draw_false_positive_multiple(): - fp_pop2 = FalsePositivePop( - elliptical_galaxy_population=lens_galaxies, - blue_galaxy_population=source_galaxies, - cosmo=cosmo, - source_number_choice=[2], - ) - draw_fp2 = fp_pop2.draw_false_positive(number=2) - assert isinstance(draw_fp2, list) - - -if __name__ == "__main__": - pytest.main() From f2148cfe6622079e2fd12e82f645336eebc64ec3 Mon Sep 17 00:00:00 2001 From: narayan Date: Fri, 25 Oct 2024 19:38:53 -0400 Subject: [PATCH 40/87] some refactoring of lens class --- slsim/lens.py | 126 ++++++++++++------------------------ slsim/lens_pop.py | 72 +++++++++++++-------- slsim/lensed_system_base.py | 25 ------- 3 files changed, 85 insertions(+), 138 deletions(-) diff --git a/slsim/lens.py b/slsim/lens.py index bb1ecda3a..28cfd777e 100644 --- a/slsim/lens.py +++ b/slsim/lens.py @@ -23,111 +23,59 @@ class Lens(LensedSystemBase): def __init__( self, - source_dict, - deflector_dict, + source_class, + deflector_class, cosmo, - deflector_type="EPL", - source_type="extended", lens_equation_solver="lenstronomy_analytical", - variability_model=None, - kwargs_variability=None, - sn_type=None, - sn_absolute_mag_band=None, - sn_absolute_zpsys=None, test_area=4 * np.pi, - mixgauss_means=None, - mixgauss_stds=None, - mixgauss_weights=None, magnification_limit=0.01, - light_profile="single_sersic", - lightcurve_time=None, los_config=None, - sn_modeldir=None, los_dict=None, - agn_driving_variability_model=None, - agn_driving_kwargs_variability=None, ): """ - :param source_dict: source properties - :type source_dict: dict or astropy table - :param deflector_dict: deflector properties - :type deflector_dict: dict + :param source_class: A Source class instance or list of Source class instance + :type source_class: Source class instance from slsim.Sources.source. Eg: + source_class=Source( + source_dict=source_dict, + variability_model=variability_model, + kwargs_variability=kwargs_variability, + sn_type=sn_type, + sn_absolute_mag_band=sn_absolute_mag_band, + sn_absolute_zpsys=sn_absolute_zpsys, + cosmo=cosmo, + lightcurve_time=lightcurve_time, + sn_modeldir=sn_modeldir, + agn_driving_variability_model=agn_driving_variability_model, + agn_driving_kwargs_variability=agn_driving_kwargs_variability, + source_type=source_type, + light_profile=light_profile, + ). See the Source class documentation. + :param deflector_class: deflector instance + :type deflector_class: Deflector class instance from slsim.Deflectors.deflector + Eg: deflector_class = Deflector( + deflector_type=deflector_type, + deflector_dict=deflector_dict, + ). See the Deflector class documentation. :param cosmo: astropy.cosmology instance - :param deflector_type: type of deflector, i.e. "EPL", "NFW_HERNQUIST", "NFW_CLUSTER" - :type deflector_type: str - :param source_type: type of the source 'extended' or 'point_source' or - 'point_plus_extended' supported - :type source_type: str :param lens_equation_solver: type of lens equation solver; currently supporting "lenstronomy_analytical" and "lenstronomy_general" :type lens_equation_solver: str - :param variability_model: keyword for variability model to be used. This is an - input for the Variability class. - :type variability_model: str - :param kwargs_variability: keyword arguments for the variability of a source. - This is associated with an input for Variability class. - :type kwargs_variability: list of str - :param sn_type: Supernova type (Ia, Ib, Ic, IIP, etc.) - :type sn_type: str - :param sn_absolute_mag_band: Band used to normalize to absolute magnitude - :type sn_absolute_mag_band: str or `~sncosmo.Bandpass` - :param sn_absolute_zpsys: Optional, AB or Vega (AB default) - :type sn_absolute_zpsys: str :param test_area: solid angle around one lensing galaxies to be investigated on (in arc-seconds^2) :param magnification_limit: absolute lensing magnification lower limit to register a point source (ignore highly de-magnified images) :type magnification_limit: float >= 0 - :param light_profile: keyword for number of sersic profile to use in source - light model - :type light_profile: str . Either "single_sersic" or "double_sersic" . - :param lightcurve_time: observation time array for lightcurve in unit of days. - :type lightcurve_time: array :param los_config: LOSConfig instance which manages line-of-sight (LOS) effects and Gaussian mixture models in a simulation or analysis context. - :param sn_modeldir: sn_modeldir is the path to the directory containing files - needed to initialize the sncosmo.model class. For example, - sn_modeldir = 'C:/Users/username/Documents/SALT3.NIR_WAVEEXT'. These data can - be downloaded from https://github.com/LSST-strong-lensing/data_public . - For more detail, please look at the documentation of RandomizedSupernovae - class. - :type sn_modeldir: str :param los_dict: line of sight dictionary (optional, takes these values instead of drawing from distribution) Takes "gamma" = [gamma1, gamma2] and "kappa" = kappa as entries :type los_dict: dict - :param agn_driving_variability_model: Variability model with light_curve output - which drives the variability across all bands of the agn. - :type agn_driving_variability_model: str (e.g. "light_curve", "sinusoidal", "bending_power_law") - :param agn_driving_kwargs_variability: Dictionary containing agn variability - parameters for the driving variability class. eg: variable_agn_kwarg_dict = - {"length_of_light_curve": 1000, "time_resolution": 1, - "log_breakpoint_frequency": 1 / 20, "low_frequency_slope": 1, - "high_frequency_slope": 3, "normal_magnitude_variance": 0.1}. For the detailed - explanation of these parameters, see generate_signal() function in - astro_util.py. - :type agn_driving_kwargs_variability: dict - """ - super().__init__( - source_dict=source_dict, - deflector_dict=deflector_dict, - cosmo=cosmo, - deflector_type=deflector_type, - test_area=test_area, - variability_model=variability_model, - kwargs_variability=kwargs_variability, - lightcurve_time=lightcurve_time, - sn_type=sn_type, - sn_absolute_mag_band=sn_absolute_mag_band, - sn_absolute_zpsys=sn_absolute_zpsys, - sn_modeldir=sn_modeldir, - agn_driving_variability_model=agn_driving_variability_model, - agn_driving_kwargs_variability=agn_driving_kwargs_variability, - source_type=source_type, - light_profile=light_profile, - ) - + """ + self.deflector = deflector_class + self.source = source_class self.cosmo = cosmo + self.test_area = test_area self._source_type = self.source.source_type self._lens_equation_solver = lens_equation_solver self._magnification_limit = magnification_limit @@ -140,9 +88,20 @@ def __init__( ) warnings.warn(warning_msg, category=UserWarning, stacklevel=2) + if isinstance(self.source, list): + self.single_source_class = max(self.source, key=lambda obj: obj.redshift) + #source_z = self.single_source_class.redshift + self._source_type = self.single_source_class.source_type + self.source_number = len(self.source) + else: + #source_z = self.source.redshift + self._source_type = self.source.source_type + self.source_number = 1 + self.single_source_class = self.source + # we conventionally use highest source redshift in the lens cosmo. self._lens_cosmo = LensCosmo( z_lens=float(self.deflector.redshift), - z_source=float(self.source.redshift), + z_source=float(self.single_source_class.redshift), cosmo=self.cosmo, ) @@ -604,9 +563,6 @@ def lenstronomy_kwargs(self, band=None): # z_source_convention: source redshift for convention of lens parameters (make sure LensCosmo has the same one) # cosmo: astropy cosmology # point_source_redshift_list: - # for multi-plane lensing only: - # lens_redshift_list: - # multi_plane: True } sources, sources_kwargs = self.source_light_model_lenstronomy(band=band) diff --git a/slsim/lens_pop.py b/slsim/lens_pop.py index 692731cc4..1aa8e2be9 100644 --- a/slsim/lens_pop.py +++ b/slsim/lens_pop.py @@ -9,6 +9,8 @@ from slsim.ParamDistributions.los_config import LOSConfig from slsim.Deflectors.deflectors_base import DeflectorsBase from slsim.lensed_population_base import LensedPopulationBase +from slsim.Sources.source import Source +from slsim.Deflectors.deflector import Deflector class LensPop(LensedPopulationBase): @@ -84,28 +86,35 @@ def select_lens_at_random(self, test_area=None, **kwargs_lens_cut): while True: source = self._sources.draw_source() lens = self._lens_galaxies.draw_deflector() + _lens = Deflector( + deflector_type=self._lens_galaxies.deflector_profile, + deflector_dict=lens, + ) if test_area is None: test_area = draw_test_area(deflector=lens) else: test_area = test_area + _source = Source( + source_dict=source, + variability_model=self._sources.variability_model, + kwargs_variability=self._sources.kwargs_variability, + sn_type=self.sn_type, + sn_absolute_mag_band=self.sn_absolute_mag_band, + sn_absolute_zpsys=self.sn_absolute_zpsys, + cosmo=self.cosmo, + lightcurve_time=self.lightcurve_time, + sn_modeldir=self.sn_modeldir, + agn_driving_variability_model=self._sources.agn_driving_variability_model, + agn_driving_kwargs_variability=self._sources.agn_driving_kwargs_variability, + source_type=self._sources.source_type, + light_profile=self._sources.light_profile, + ) gg_lens = Lens( - deflector_dict=lens, - source_dict=source, - deflector_type=self._lens_galaxies.deflector_profile, - variability_model=self._sources.variability_model, - kwargs_variability=self._sources.kwargs_variability, - sn_type=self.sn_type, - sn_absolute_mag_band=self.sn_absolute_mag_band, - sn_absolute_zpsys=self.sn_absolute_zpsys, + deflector_class=_lens, + source_class=_source, cosmo=self.cosmo, test_area=test_area, - source_type=self._sources.source_type, - light_profile=self._sources.light_profile, - lightcurve_time=self.lightcurve_time, los_config=self.los_config, - sn_modeldir=self.sn_modeldir, - agn_driving_variability_model=self._sources.agn_driving_variability_model, - agn_driving_kwargs_variability=self._sources.agn_driving_kwargs_variability, ) if gg_lens.validity_test(**kwargs_lens_cut): return gg_lens @@ -178,29 +187,36 @@ def draw_population(self, kwargs_lens_cuts, speed_factor=1): num_sources_tested = self.get_num_sources_tested( testarea=test_area * speed_factor ) + _lens = Deflector( + deflector_type=self._lens_galaxies.deflector_profile, + deflector_dict=lens, + ) # TODO: to implement this for a multi-source plane lens system if num_sources_tested > 0: n = 0 while n < num_sources_tested: source = self._sources.draw_source() + _source = Source( + source_dict=source, + variability_model=self._sources.variability_model, + kwargs_variability=self._sources.kwargs_variability, + sn_type=self.sn_type, + sn_absolute_mag_band=self.sn_absolute_mag_band, + sn_absolute_zpsys=self.sn_absolute_zpsys, + cosmo=self.cosmo, + lightcurve_time=self.lightcurve_time, + sn_modeldir=self.sn_modeldir, + agn_driving_variability_model=self._sources.agn_driving_variability_model, + agn_driving_kwargs_variability=self._sources.agn_driving_kwargs_variability, + source_type=self._sources.source_type, + light_profile=self._sources.light_profile, + ) gg_lens = Lens( - deflector_dict=lens, - source_dict=source, - deflector_type=self._lens_galaxies.deflector_profile, - variability_model=self._sources.variability_model, - kwargs_variability=self._sources.kwargs_variability, - sn_type=self.sn_type, - sn_absolute_mag_band=self.sn_absolute_mag_band, - sn_absolute_zpsys=self.sn_absolute_zpsys, + deflector_class=_lens, + source_class=_source, cosmo=self.cosmo, test_area=test_area, - source_type=self._sources.source_type, los_config=self.los_config, - light_profile=self._sources.light_profile, - lightcurve_time=self.lightcurve_time, - sn_modeldir=self.sn_modeldir, - agn_driving_variability_model=self._sources.agn_driving_variability_model, - agn_driving_kwargs_variability=self._sources.agn_driving_kwargs_variability, ) # Check the validity of the lens system if gg_lens.validity_test(**kwargs_lens_cuts): diff --git a/slsim/lensed_system_base.py b/slsim/lensed_system_base.py index 3d900b77d..ad16f8667 100644 --- a/slsim/lensed_system_base.py +++ b/slsim/lensed_system_base.py @@ -1,8 +1,5 @@ from abc import ABC, abstractmethod import numpy as np -from slsim.Sources.source import Source -from slsim.Deflectors.deflector import Deflector - class LensedSystemBase(ABC): """Abstract Base class to create a lens system with all lensing properties required @@ -74,28 +71,6 @@ def __init__( light model :type light_profile: str . Either "single_sersic" or "double_sersic" . """ - self.source = Source( - source_dict=source_dict, - variability_model=variability_model, - kwargs_variability=kwargs_variability, - sn_type=sn_type, - sn_absolute_mag_band=sn_absolute_mag_band, - sn_absolute_zpsys=sn_absolute_zpsys, - cosmo=cosmo, - lightcurve_time=lightcurve_time, - sn_modeldir=sn_modeldir, - agn_driving_variability_model=agn_driving_variability_model, - agn_driving_kwargs_variability=agn_driving_kwargs_variability, - source_type=source_type, - light_profile=light_profile, - ) - self.deflector = Deflector( - deflector_type=deflector_type, - deflector_dict=deflector_dict, - ) - # TODO: tell them what keys the dictionary should contain - self.test_area = test_area - self.cosmo = cosmo @abstractmethod def deflector_position(self): From 1a53a37ed09db046da5d951c44a1fa45febcadd9 Mon Sep 17 00:00:00 2001 From: narayan Date: Fri, 25 Oct 2024 19:43:13 -0400 Subject: [PATCH 41/87] minor change --- slsim/Sources/galaxies.py | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/slsim/Sources/galaxies.py b/slsim/Sources/galaxies.py index 146f13c40..52100beca 100644 --- a/slsim/Sources/galaxies.py +++ b/slsim/Sources/galaxies.py @@ -120,24 +120,14 @@ def source_number_selected(self): """ return self._num_select - def draw_source(self, z_max=None): - """Choose source at random. :param z_max: maximum redshift for source to be - drawn. + def draw_source(self): + """Choose source at random. - :param z_max: maximum redshift limit for the galaxy to be drawn. If no galaxy is - found for this limit, None will be returned. :return: dictionary of source """ - if z_max is not None: - filtered_galaxies = self._galaxy_select[self._galaxy_select["z"] < z_max] - if len(filtered_galaxies) == 0: - return None - else: - index = random.randint(0, len(filtered_galaxies) - 1) - galaxy = filtered_galaxies[index] - else: - index = random.randint(0, self._num_select - 1) - galaxy = self._galaxy_select[index] + + index = random.randint(0, self._num_select - 1) + galaxy = self._galaxy_select[index] if "a_rot" in galaxy.colnames: phi_rot = galaxy["a_rot"] else: @@ -283,4 +273,4 @@ def convert_to_slsim_convention( galaxy_catalog["a_rot"] = np.deg2rad(galaxy_catalog["a_rot"]) if input_catalog_type == "skypy": galaxy_catalog["angular_size"] = galaxy_catalog["angular_size"].to(u.arcsec) - return galaxy_catalog + return galaxy_catalog \ No newline at end of file From 7c2db5ccfb21f24b771348a84c40bc756a72f978 Mon Sep 17 00:00:00 2001 From: narayan Date: Mon, 28 Oct 2024 17:05:52 -0400 Subject: [PATCH 42/87] adjusted test functions for lens class with current changes. --- slsim/lens.py | 8 ---- tests/test_lens.py | 115 +++++++++++++++++++++++++++++++++------------ 2 files changed, 86 insertions(+), 37 deletions(-) diff --git a/slsim/lens.py b/slsim/lens.py index 28cfd777e..2552a81a0 100644 --- a/slsim/lens.py +++ b/slsim/lens.py @@ -79,14 +79,6 @@ def __init__( self._source_type = self.source.source_type self._lens_equation_solver = lens_equation_solver self._magnification_limit = magnification_limit - self._kwargs_variab = self.source.kwargs_variability - - if self._source_type == "extended" and self._kwargs_variab is not None: - warning_msg = ( - "Extended source can not have variability. Therefore," - "variability information provided by you will not be used." - ) - warnings.warn(warning_msg, category=UserWarning, stacklevel=2) if isinstance(self.source, list): self.single_source_class = max(self.source, key=lambda obj: obj.redshift) diff --git a/tests/test_lens.py b/tests/test_lens.py index a2c2c6ad9..7f189f4f2 100644 --- a/tests/test_lens.py +++ b/tests/test_lens.py @@ -9,6 +9,8 @@ theta_e_when_source_infinity, ) from slsim.ParamDistributions.los_config import LOSConfig +from slsim.Sources.source import Source +from slsim.Deflectors.deflector import Deflector import os @@ -36,11 +38,21 @@ def setup_method(self): blue_one["gamma_pl"] = 2.1 mag_arc_limit = {"i": 35, "g": 35, "r": 35} while True: - gg_lens = Lens( - source_dict=self.source_dict, + self.source = Source( + source_dict=self.source_dict, + cosmo=cosmo, + source_type="extended", + light_profile="single_sersic", + ) + self.deflector = Deflector( + deflector_type="EPL", deflector_dict=self.deflector_dict, + ) + gg_lens = Lens( + source_class=self.source, + deflector_class=self.deflector, lens_equation_solver="lenstronomy_analytical", - kwargs_variability={"MJD", "ps_mag_i"}, # This line will not be used in + #kwargs_variability={"MJD", "ps_mag_i"}, # This line will not be used in # the testing but at least code go through this warning message. cosmo=cosmo, ) @@ -126,12 +138,12 @@ def test_deflector_light_model_lenstronomy(self): assert len(kwargs_lens_light) >= 1 def test_lens_equation_solver(self): - """Tests analytical and numerical lens equation solver options.""" + #Tests analytical and numerical lens equation solver options. cosmo = FlatLambdaCDM(H0=70, Om0=0.3) gg_lens = Lens( lens_equation_solver="lenstronomy_default", - source_dict=self.source_dict, - deflector_dict=self.deflector_dict, + source_class=self.source, + deflector_class=self.deflector, cosmo=cosmo, ) while True: @@ -140,8 +152,8 @@ def test_lens_equation_solver(self): gg_lens = Lens( lens_equation_solver="lenstronomy_analytical", - source_dict=self.source_dict, - deflector_dict=self.deflector_dict, + source_class=self.source, + deflector_class=self.deflector, cosmo=cosmo, ) while True: @@ -171,10 +183,19 @@ def test_lens_equation_solver(self): } while True: - gg_lens = Lens( - source_dict=source_dict, - deflector_dict=deflector_dict, + self.source2 = Source( + source_dict=source_dict, + cosmo=cosmo, + source_type="extended", + light_profile="single_sersic", + ) + self.deflector2 = Deflector( deflector_type="NFW_HERNQUIST", + deflector_dict=deflector_dict, + ) + gg_lens = Lens( + source_class=self.source2, + deflector_class=self.deflector2, lens_equation_solver="lenstronomy_default", cosmo=cosmo, ) @@ -196,10 +217,19 @@ def test_lens_equation_solver(self): "subhalos": subhalos_table, } while True: - cg_lens = Lens( - source_dict=source_dict, - deflector_dict=deflector_dict, + self.source3 = Source( + source_dict=source_dict, + cosmo=cosmo, + source_type="extended", + light_profile="single_sersic", + ) + self.deflector3 = Deflector( deflector_type="NFW_CLUSTER", + deflector_dict=deflector_dict, + ) + cg_lens = Lens( + source_class=self.source3, + deflector_class=self.deflector3, lens_equation_solver="lenstronomy_default", cosmo=cosmo, ) @@ -236,12 +266,21 @@ def pes_lens_instance(): cosmo = FlatLambdaCDM(H0=70, Om0=0.3) while True: - pes_lens = Lens( + source4 = Source( source_dict=source_dict, - deflector_dict=deflector_dict, + cosmo=cosmo, source_type="point_plus_extended", + light_profile="single_sersic", variability_model="sinusoidal", kwargs_variability={"amp", "freq"}, + ) + deflector4 = Deflector( + deflector_type="EPL", + deflector_dict=deflector_dict, + ) + pes_lens = Lens( + source_class=source4, + deflector_class=deflector4, cosmo=cosmo, ) if pes_lens.validity_test(): @@ -270,12 +309,21 @@ def supernovae_lens_instance(): cosmo = FlatLambdaCDM(H0=70, Om0=0.3) while True: - supernovae_lens = Lens( + source5 = Source( source_dict=source_dict, - deflector_dict=deflector_dict, + cosmo=cosmo, source_type="point_plus_extended", + light_profile="single_sersic", variability_model="light_curve", kwargs_variability={"MJD", "ps_mag_r"}, + ) + deflector5 = Deflector( + deflector_type="EPL", + deflector_dict=deflector_dict, + ) + supernovae_lens = Lens( + source_class=source5, + deflector_class=deflector5, cosmo=cosmo, ) if supernovae_lens.validity_test(): @@ -309,7 +357,16 @@ def setup_method(self): self.cosmo = FlatLambdaCDM(H0=70, Om0=0.3) self.source_dict = blue_one self.deflector_dict = red_one - + self.source6 = Source( + source_dict=self.source_dict, + cosmo=self.cosmo, + source_type="extended", + light_profile="single_sersic", + ) + self.deflector6 = Deflector( + deflector_type="EPL", + deflector_dict=self.deflector_dict, + ) def test_different_setting(self): los1 = LOSConfig( los_bool=True, @@ -317,8 +374,8 @@ def test_different_setting(self): nonlinear_los_bool=False, ) gg_lens = Lens( - source_dict=self.source_dict, - deflector_dict=self.deflector_dict, + source_class=self.source6, + deflector_class=self.deflector6, cosmo=self.cosmo, los_config=los1, ) @@ -333,8 +390,8 @@ def test_different_setting(self): ) gg_lens_2 = Lens( - source_dict=self.source_dict, - deflector_dict=self.deflector_dict, + source_class=self.source6, + deflector_class=self.deflector6, cosmo=self.cosmo, los_config=los2, ) @@ -344,8 +401,8 @@ def test_different_setting(self): los3 = LOSConfig(los_bool=False) gg_lens_3 = Lens( - source_dict=self.source_dict, - deflector_dict=self.deflector_dict, + source_class=self.source6, + deflector_class=self.deflector6, cosmo=self.cosmo, los_config=los3, ) @@ -359,8 +416,8 @@ def test_different_setting(self): ) with pytest.raises(ValueError): gg_lens_4 = Lens( - source_dict=self.source_dict, - deflector_dict=self.deflector_dict, + source_class=self.source6, + deflector_class=self.deflector6, cosmo=self.cosmo, los_config=los4, ) @@ -373,8 +430,8 @@ def test_image_number(self): nonlinear_los_bool=False, ) gg_lens_number = Lens( - source_dict=self.source_dict, - deflector_dict=self.deflector_dict, + source_class=self.source6, + deflector_class=self.deflector6, cosmo=self.cosmo, los_config=los, ) From be7c55d90e672d0204ad094b0ab50e14b00d4b6b Mon Sep 17 00:00:00 2001 From: narayan Date: Tue, 29 Oct 2024 14:47:56 -0400 Subject: [PATCH 43/87] added multi source option in Lens class. --- slsim/lens.py | 479 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 288 insertions(+), 191 deletions(-) diff --git a/slsim/lens.py b/slsim/lens.py index 2552a81a0..e9ecf7f70 100644 --- a/slsim/lens.py +++ b/slsim/lens.py @@ -1,5 +1,3 @@ -import warnings - import numpy as np from lenstronomy.Analysis.lens_profile import LensProfileAnalysis from lenstronomy.Cosmo.lens_cosmo import LensCosmo @@ -73,23 +71,22 @@ def __init__( :type los_dict: dict """ self.deflector = deflector_class - self.source = source_class self.cosmo = cosmo self.test_area = test_area - self._source_type = self.source.source_type self._lens_equation_solver = lens_equation_solver self._magnification_limit = magnification_limit - if isinstance(self.source, list): + if isinstance(source_class, list): + self.source = source_class self.single_source_class = max(self.source, key=lambda obj: obj.redshift) - #source_z = self.single_source_class.redshift - self._source_type = self.single_source_class.source_type self.source_number = len(self.source) + self._single_source_index = self.source.index(self.single_source_class) else: - #source_z = self.source.redshift - self._source_type = self.source.source_type + self.source = [source_class] self.source_number = 1 - self.single_source_class = self.source + self.single_source_class = source_class + self._single_source_index = 0 + self._source_type = self.single_source_class.source_type # we conventionally use highest source redshift in the lens cosmo. self._lens_cosmo = LensCosmo( z_lens=float(self.deflector.redshift), @@ -110,7 +107,7 @@ def image_number(self): :return: number of images """ - return len(self.point_source_image_positions()[0]) + return [len(pos[0]) for pos in self.point_source_image_positions()] @property def deflector_position(self): @@ -129,25 +126,27 @@ def extended_source_image_positions(self): lens_model_list, kwargs_lens = self.deflector_mass_model_lenstronomy() lens_model_class = LensModel(lens_model_list=lens_model_list) lens_eq_solver = LensEquationSolver(lens_model_class) - source_pos_x, source_pos_y = self.source.extended_source_position( - center_lens=self.deflector_position, draw_area=self.test_area - ) - if ( - self._lens_equation_solver == "lenstronomy_analytical" - and analytical_lens_model_support(lens_model_list) is True - ): - solver = "analytical" - else: - solver = "lenstronomy" - self._image_positions = lens_eq_solver.image_position_from_source( - source_pos_x, - source_pos_y, - kwargs_lens, - solver=solver, - search_window=self.einstein_radius * 6, - min_distance=self.einstein_radius * 6 / 200, - magnification_limit=self._magnification_limit, - ) + self._image_positions = [] + for source, einstein_radius in zip(self.source, self.einstein_radius): + source_pos_x, source_pos_y = source.extended_source_position( + center_lens=self.deflector_position, draw_area=self.test_area + ) + if ( + self._lens_equation_solver == "lenstronomy_analytical" + and analytical_lens_model_support(lens_model_list) is True + ): + solver = "analytical" + else: + solver = "lenstronomy" + self._image_positions.append(lens_eq_solver.image_position_from_source( + source_pos_x, + source_pos_y, + kwargs_lens, + solver=solver, + search_window=einstein_radius * 6, + min_distance=einstein_radius * 6 / 200, + magnification_limit=self._magnification_limit, + )) return self._image_positions def point_source_image_positions(self): @@ -161,26 +160,28 @@ def point_source_image_positions(self): lens_model_list, kwargs_lens = self.deflector_mass_model_lenstronomy() lens_model_class = LensModel(lens_model_list=lens_model_list) lens_eq_solver = LensEquationSolver(lens_model_class) - point_source_pos_x, point_source_pos_y = self.source.point_source_position( - center_lens=self.deflector_position, draw_area=self.test_area - ) - # uses analytical lens equation solver in case it is supported by lenstronomy for speed-up - if ( - self._lens_equation_solver == "lenstronomy_analytical" - and analytical_lens_model_support(lens_model_list) is True - ): - solver = "analytical" - else: - solver = "lenstronomy" - self._point_image_positions = lens_eq_solver.image_position_from_source( - point_source_pos_x, - point_source_pos_y, - kwargs_lens, - solver=solver, - search_window=self.einstein_radius * 6, - min_distance=self.einstein_radius * 6 / 200, - magnification_limit=self._magnification_limit, - ) + self._point_image_positions = [] + for source, einstein_radius in zip(self.source, self.einstein_radius): + point_source_pos_x, point_source_pos_y = source.point_source_position( + center_lens=self.deflector_position, draw_area=self.test_area + ) + # uses analytical lens equation solver in case it is supported by lenstronomy for speed-up + if ( + self._lens_equation_solver == "lenstronomy_analytical" + and analytical_lens_model_support(lens_model_list) is True + ): + solver = "analytical" + else: + solver = "lenstronomy" + self._point_image_positions.append(lens_eq_solver.image_position_from_source( + point_source_pos_x, + point_source_pos_y, + kwargs_lens, + solver=solver, + search_window=einstein_radius * 6, + min_distance=einstein_radius * 6 / 200, + magnification_limit=self._magnification_limit, + )) return self._point_image_positions def validity_test( @@ -188,6 +189,23 @@ def validity_test( min_image_separation=0, max_image_separation=10, mag_arc_limit=None, + ): + """Check if the lensing configuration is valid for each source.""" + for source, einstein_radius, image_pos in zip(self.source, self.einstein_radius, + self.point_source_image_positions()): + if not self._validity_test(source, einstein_radius, image_pos, + min_image_separation, max_image_separation, mag_arc_limit): + return False + return True + + def _validity_test( + self, + source, + einstein_radius, + image_positions, + min_image_separation=0, + max_image_separation=10, + mag_arc_limit=None, ): """Check whether lensing configuration matches selection and plausibility criteria. @@ -202,7 +220,7 @@ def validity_test( # Criteria 1:The redshift of the lens (z_lens) must be less than the # redshift of the source (z_source). z_lens = self.deflector.redshift - z_source = self.source.redshift + z_source = source.redshift if z_lens >= z_source: return False @@ -210,7 +228,7 @@ def validity_test( # times 2 must be greater than or equal to the minimum image separation # (min_image_separation) and less than or equal to the maximum image # separation (max_image_separation). - if not min_image_separation <= 2 * self.einstein_radius <= max_image_separation: + if not min_image_separation <= 2 * einstein_radius <= max_image_separation: return False # Criteria 3: The distance between the lens center and the source position @@ -218,15 +236,15 @@ def validity_test( # of the lensing configuration (times sqrt(2)). center_lens, center_source = ( self.deflector_position, - self.source.point_source_position( + source.point_source_position( center_lens=self.deflector_position, draw_area=self.test_area ), ) - if np.sum((center_lens - center_source) ** 2) > self.einstein_radius**2 * 2: + if np.sum((center_lens - center_source) ** 2) > einstein_radius**2 * 2: return False # Criteria 4: The lensing configuration must produce at least two SL images. - image_positions = self.point_source_image_positions() + image_positions = image_positions if len(image_positions[0]) < 2: return False @@ -274,9 +292,12 @@ def deflector_redshift(self): def source_redshift(self): """ - :return: source redshift + :return: list of source redshifts """ - return self.source.redshift + source_redshift_list = [] + for source in self.source: + source_redshift_list.append(source.redshift) + return source_redshift_list @property def external_convergence(self): @@ -303,33 +324,43 @@ def einstein_radius_deflector(self): :return: """ - if not hasattr(self, "_theta_E"): - if self.deflector.redshift >= self.source.redshift: - self._theta_E = 0 - elif self.deflector.deflector_type in ["EPL"]: - self._theta_E = self._lens_cosmo.sis_sigma_v2theta_E( - float(self.deflector.velocity_dispersion(cosmo=self.cosmo)) - ) - else: - # numerical solution for the Einstein radius - lens_model_list, kwargs_lens = self.deflector_mass_model_lenstronomy() - lens_model = LensModel(lens_model_list=lens_model_list) - lens_analysis = LensProfileAnalysis(lens_model=lens_model) - self._theta_E = lens_analysis.effective_einstein_radius( - kwargs_lens, r_min=1e-3, r_max=5e1, num_points=50 - ) - return self._theta_E + if not hasattr(self, "_theta_E_list"): + self._theta_E_list = [] + for source in self.source: + if self.deflector.redshift >= source.redshift: + self._theta_E_list.append(0) + elif self.deflector.deflector_type in ["EPL"]: + _lens_cosmo = LensCosmo( + z_lens=float(self.deflector.redshift), + z_source=float(source.redshift), + cosmo=self.cosmo, + ) + _theta_E = _lens_cosmo.sis_sigma_v2theta_E( + float(self.deflector.velocity_dispersion(cosmo=self.cosmo)) + ) + self._theta_E_list.append(_theta_E) + else: + # numerical solution for the Einstein radius + lens_model_list, kwargs_lens = self.deflector_mass_model_lenstronomy() + lens_model = LensModel(lens_model_list=lens_model_list) + lens_analysis = LensProfileAnalysis(lens_model=lens_model) + _theta_E = lens_analysis.effective_einstein_radius( + kwargs_lens, r_min=1e-3, r_max=5e1, num_points=50 + ) + self._theta_E_list.append(_theta_E) + return self._theta_E_list @property def einstein_radius(self): """Einstein radius, from SIS approximation (coming from velocity dispersion) + - external convergence effect. + external convergence effect for each lens-source pair. :return: Einstein radius [arc seconds] """ - theta_E = self.einstein_radius_deflector - _, _, kappa_ext = self.los_linear_distortions - return theta_E / (1 - kappa_ext) + theta_E_list = self.einstein_radius_deflector + los_distortions = self.los_linear_distortions + return [theta_E / (1 - kappa_ext) for theta_E, (_, _, kappa_ext) in zip( + theta_E_list, los_distortions)] def deflector_ellipticity(self): """ @@ -363,14 +394,18 @@ def los_linear_distortions(self): return self._los_linear_distortions_cache def _calculate_los_linear_distortions(self): - """Line-of-sight distortions in shear and convergence. + """Line-of-sight distortions in shear and convergence for each source. :return: kappa, gamma1, gamma2 """ - return self.los_config.calculate_los_linear_distortions( - source_redshift=self.source_redshift, - deflector_redshift=self.deflector_redshift, - ) + los_distortions = [] + for source in self.source: + gamma1, gamma2, kappa = self.los_config.calculate_los_linear_distortions( + source_redshift=source.redshift, + deflector_redshift=self.deflector_redshift + ) + los_distortions.append((gamma1, gamma2, kappa)) + return los_distortions def deflector_magnitude(self, band): """Apparent magnitude of the deflector for a given band. @@ -384,23 +419,27 @@ def deflector_magnitude(self, band): def point_source_arrival_times(self): """Arrival time of images relative to a straight line without lensing. Negative values correspond to images arriving earlier, and positive signs correspond to - images arriving later. - - :return: arrival times for each image [days] - :rtype: numpy array - """ - lens_model_list, kwargs_lens = self.deflector_mass_model_lenstronomy() - lens_model = LensModel( - lens_model_list=lens_model_list, - cosmo=self.cosmo, - z_lens=self.deflector_redshift, - z_source=self.source_redshift, - ) - x_image, y_image = self.point_source_image_positions() - arrival_times = lens_model.arrival_time( - x_image, y_image, kwargs_lens=kwargs_lens - ) - return arrival_times + images arriving later. This is for single source. + + :return: list of arrival times for each image [days] for each source. + :rtype: list of numpy array + """ + lens_model_list, kwargs_lens = self.deflector_mass_model_lenstronomy() + image_pos = self.point_source_image_positions() + arrival_times_list = [] + for index, source in enumerate(self.source): + lens_model = LensModel( + lens_model_list=lens_model_list, + cosmo=self.cosmo, + z_lens=self.deflector_redshift, + z_source=source.redshift, + ) + x_image, y_image = image_pos[index] + arrival_times = lens_model.arrival_time( + x_image, y_image, kwargs_lens=kwargs_lens + ) + arrival_times_list.append(arrival_times) + return arrival_times_list def image_observer_times(self, t_obs): """Calculates time of the source at the different images, not correcting for @@ -414,20 +453,24 @@ def image_observer_times(self, t_obs): :rtype: numpy array. Each element of the array corresponds to different image observation times. """ - arrival_times = self.point_source_arrival_times() - if type(t_obs) is np.ndarray and len(t_obs) > 1: - observer_times = ( - t_obs[:, np.newaxis] - arrival_times + np.min(arrival_times) - ).T - else: - observer_times = (t_obs - arrival_times + np.min(arrival_times))[ - :, np.newaxis - ] - - return observer_times + observer_times_list = [] + for point_source_arrival_time in self.point_source_arrival_times(): + arrival_times = point_source_arrival_time + if type(t_obs) is np.ndarray and len(t_obs) > 1: + observer_times = ( + t_obs[:, np.newaxis] - arrival_times + np.min(arrival_times) + ).T + else: + observer_times = (t_obs - arrival_times + np.min(arrival_times))[ + :, np.newaxis + ] + observer_times_list.append(observer_times) + if self.source_number == 1: + return observer_times_list[0] + return observer_times_list def point_source_magnitude(self, band, lensed=False, time=None): - """Point source magnitude, either unlensed (single value) or lensed (array) with + """Point source magnitudes, either unlensed (single value) or lensed (array) with macro-model magnifications. # TODO: time-variability with micro-lensing @@ -438,30 +481,38 @@ def point_source_magnitude(self, band, lensed=False, time=None): :type lensed: bool :param time: time is a image observation time in units of days. If None, provides magnitude without variability. - :return: point source magnitude + :return: point source magnitude or a list of point source magnitudes. """ - # TODO: might have to change conventions between extended and point source + if lensed: - magnif = self.point_source_magnification() - magnif_log = 2.5 * np.log10(abs(magnif)) - if time is not None: - time = time - image_observed_times = self.image_observer_times(time) - variable_magnitude = self.source.point_source_magnitude( - band, - image_observation_times=image_observed_times, - ) - lensed_variable_magnitude = ( - variable_magnitude - magnif_log[:, np.newaxis] - ) - return lensed_variable_magnitude - else: - source_mag_unlensed = self.source.point_source_magnitude(band) - magnified_mag_list = [] - for i in range(len(magnif_log)): - magnified_mag_list.append(source_mag_unlensed - magnif_log[i]) - return np.array(magnified_mag_list) - return self.source.point_source_magnitude(band) + magnif_list = self.point_source_magnification() + magnif_log_list = 2.5 * np.log10(abs(magnif_list)) + #loop through all the source + magnitude_list = [] + for index, (source, magnif_log) in enumerate(zip( + self.source, magnif_log_list)): + if time is not None: + time = time + image_observed_times = self.image_observer_times(time)[index] + variable_magnitude = source.point_source_magnitude( + band, + image_observation_times=image_observed_times, + ) + lensed_variable_magnitude = ( + variable_magnitude - magnif_log[:, np.newaxis] + ) + magnitude_list.append(lensed_variable_magnitude) + else: + source_mag_unlensed = source.point_source_magnitude(band) + magnified_mag_list = [] + for i in range(len(magnif_log)): + magnified_mag_list.append(source_mag_unlensed - magnif_log[i]) + magnitude_list.append(np.array(magnified_mag_list)) + else: + magnitude_list = [] + for source in self.source: + magnitude_list.append(source.point_source_magnitude(band)) + return magnitude_list def extended_source_magnitude(self, band, lensed=False): """Unlensed apparent magnitude of the extended source for a given band (assumes @@ -471,69 +522,96 @@ def extended_source_magnitude(self, band, lensed=False): :type band: string :param lensed: if True, returns the lensed magnified magnitude :type lensed: bool - :return: magnitude of source in given band + :return: magnitude of source in given band or list of magnitude of each source. """ # band_string = str("mag_" + band) # TODO: might have to change conventions between extended and point source - source_mag = self.source.extended_source_magnitude(band) - if lensed: - mag = self.extended_source_magnification() - return source_mag - 2.5 * np.log10(mag) - return source_mag + magnification_list = self.extended_source_magnification() + magnitude_list = [] + #loop through each source. + for index, source in enumerate(self.source): + source_mag = source.extended_source_magnitude(band) + if lensed: + mag = magnification_list[index] + lensed_mag = source_mag - 2.5 * np.log10(mag) + magnitude_list.append(lensed_mag) + else: + magnitude_list.append(source_mag) + return magnitude_list def point_source_magnification(self): """Macro-model magnification of point sources. - :return: signed magnification of point sources in same order as image positions + :return: list of signed magnification of point sources in same order as + image positions. """ if not hasattr(self, "_ps_magnification"): lens_model_list, kwargs_lens = self.deflector_mass_model_lenstronomy() lensModel = LensModel(lens_model_list=lens_model_list) - img_x, img_y = self.point_source_image_positions() - self._ps_magnification = lensModel.magnification(img_x, img_y, kwargs_lens) - return self._ps_magnification + self._ps_magnification_list = [] + for image_pos in self.point_source_image_positions(): + img_x, img_y = image_pos + self._ps_magnification_list.append(lensModel.magnification(img_x, img_y, + kwargs_lens)) + return self._ps_magnification_list def extended_source_magnification(self): """Compute the extended lensed surface brightness and calculates the integrated - flux-weighted magnification factor of the extended host galaxy. + flux-weighted magnification factor of each extended host galaxy . - :return: integrated magnification factor of host magnitude + :return: list of integrated magnification factor of host magnitude """ if not hasattr(self, "_extended_source_magnification"): kwargs_model, kwargs_params = self.lenstronomy_kwargs(band=None) - lightModel = LightModel( - light_model_list=kwargs_model.get("source_light_model_list", []) - ) - lensModel = LensModel( - lens_model_list=kwargs_model.get("lens_model_list", []) - ) - theta_E = self.einstein_radius - center_source = self.source.extended_source_position( - center_lens=self.deflector_position, draw_area=self.test_area - ) + theta_E_list = self.einstein_radius + self._extended_source_magnification_list = [] + # loop through each source. + for index, source in enumerate(self.source): + """lightModel = LightModel( + light_model_list=kwargs_model.get( + "source_light_model_list", []) + ) + kwargs_source_mag = kwargs_params["kwargs_source"]""" + _light_model_list = kwargs_model.get( + "source_light_model_list", [])[index] + kwargs_source_mag = [kwargs_params["kwargs_source"][index]] + if isinstance(_light_model_list, list): + light_model_list = _light_model_list + else: + light_model_list = [_light_model_list] + lightModel = LightModel( + light_model_list=light_model_list) + lensModel = LensModel( + lens_model_list=kwargs_model.get("lens_model_list", []) + ) + theta_E = theta_E_list[index] + center_source = source.extended_source_position( + center_lens=self.deflector_position, draw_area=self.test_area + ) - kwargs_source_mag = kwargs_params["kwargs_source"] - kwargs_source_amp = data_util.magnitude2amplitude( - lightModel, kwargs_source_mag, magnitude_zero_point=0 - ) + kwargs_source_amp = data_util.magnitude2amplitude( + lightModel, kwargs_source_mag, magnitude_zero_point=0 + ) - num_pix = 200 - delta_pix = theta_E * 4 / num_pix - x, y = util.make_grid(numPix=num_pix, deltapix=delta_pix) - x += center_source[0] - y += center_source[1] - beta_x, beta_y = lensModel.ray_shooting(x, y, kwargs_params["kwargs_lens"]) - flux_lensed = np.sum( - lightModel.surface_brightness(beta_x, beta_y, kwargs_source_amp) - ) - flux_no_lens = np.sum( - lightModel.surface_brightness(x, y, kwargs_source_amp) - ) - if flux_no_lens > 0: - self._extended_source_magnification = flux_lensed / flux_no_lens - else: - self._extended_source_magnification = 0 - return self._extended_source_magnification + num_pix = 200 + delta_pix = theta_E * 4 / num_pix + x, y = util.make_grid(numPix=num_pix, deltapix=delta_pix) + x += center_source[0] + y += center_source[1] + beta_x, beta_y = lensModel.ray_shooting(x, y, kwargs_params["kwargs_lens"]) + flux_lensed = np.sum( + lightModel.surface_brightness(beta_x, beta_y, kwargs_source_amp) + ) + flux_no_lens = np.sum( + lightModel.surface_brightness(x, y, kwargs_source_amp) + ) + if flux_no_lens > 0: + self._extended_source_magnification = flux_lensed / flux_no_lens + else: + self._extended_source_magnification = 0 + self._extended_source_magnification_list.append( + self._extended_source_magnification) + return self._extended_source_magnification_list def lenstronomy_kwargs(self, band=None): """Generates lenstronomy dictionary conventions for the class object. @@ -589,7 +667,8 @@ def deflector_mass_model_lenstronomy(self): % self.deflector.deflector_type ) # adding line-of-sight structure - gamma1, gamma2, kappa_ext = self.los_linear_distortions + gamma1, gamma2, kappa_ext = self.los_linear_distortions[ + self._single_source_index] gamma1_lenstronomy, gamma2_lenstronomy = ellipticity_slsim_to_lenstronomy( e1_slsim=gamma1, e2_slsim=gamma2 ) @@ -628,11 +707,21 @@ def source_light_model_lenstronomy(self, band=None): self._source_type == "extended" or self._source_type == "point_plus_extended" ): - source_models["source_light_model_list" - ] = self.source.extended_source_light_model() - kwargs_source = self.source.kwargs_extended_source_light( + source_models_list = [] + kwargs_source_list = [] + for source in self.source: + source_models_list.append(source.extended_source_light_model()[0]) + kwargs_source_list.append(source.kwargs_extended_source_light( draw_area=self.test_area, center_lens=self.deflector_position, band=band - ) + )[0]) + """if self.source_number == 1: + _source_models_list = [item[0] for item in source_models_list] + _kwargs_source_list = kwargs_source_list[0] + else: + _source_models_list = source_models_list + _kwargs_source_list = kwargs_source_list""" + source_models["source_light_model_list"] = source_models_list + kwargs_source = kwargs_source_list else: # source_models['source_light_model_list'] = None kwargs_source = None @@ -641,15 +730,24 @@ def source_light_model_lenstronomy(self, band=None): self._source_type == "point_source" or self._source_type == "point_plus_extended" ): - source_models["point_source_model_list"] = ["LENSED_POSITION"] - img_x, img_y = self.point_source_image_positions() - if band is None: - image_magnitudes = np.abs(self.point_source_magnification()) - else: - image_magnitudes = self.point_source_magnitude(band=band, lensed=True) - kwargs_ps = [ - {"ra_image": img_x, "dec_image": img_y, "magnitude": image_magnitudes} - ] + image_pos_list = self.point_source_image_positions() + image_magnif_list = np.abs(self.point_source_magnification()) + magnitude_list = self.point_source_magnitude( + band=band, lensed=True) + source_models_list = [] + kwargs_ps_list = [] + for index, source in enumerate(self.source): + source_models_list.append("LENSED_POSITION") + img_x, img_y = image_pos_list[index] + if band is None: + image_magnitudes = image_magnif_list[index] + else: + image_magnitudes = magnitude_list[index] + kwargs_ps_list.append( + {"ra_image": img_x, "dec_image": img_y, + "magnitude": image_magnitudes}) + source_models["point_source_model_list"] = source_models_list + kwargs_ps = kwargs_ps_list else: # source_models['point_source_model'] = None kwargs_ps = None @@ -684,7 +782,6 @@ def kappa_star(self, ra, dec): ) return kappa_star - def image_separation_from_positions(image_positions): """Calculate image separation in arc-seconds; if there are only two images, the separation between them is returned; if there are more than 2 images, the maximum From 4eaa19ea5566161209470bab9aa9a6e67726bfdb Mon Sep 17 00:00:00 2001 From: narayan Date: Tue, 29 Oct 2024 17:07:08 -0400 Subject: [PATCH 44/87] added multi source option in Lens class. --- slsim/lens.py | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/slsim/lens.py b/slsim/lens.py index e9ecf7f70..b8614f984 100644 --- a/slsim/lens.py +++ b/slsim/lens.py @@ -567,11 +567,6 @@ def extended_source_magnification(self): self._extended_source_magnification_list = [] # loop through each source. for index, source in enumerate(self.source): - """lightModel = LightModel( - light_model_list=kwargs_model.get( - "source_light_model_list", []) - ) - kwargs_source_mag = kwargs_params["kwargs_source"]""" _light_model_list = kwargs_model.get( "source_light_model_list", [])[index] kwargs_source_mag = [kwargs_params["kwargs_source"][index]] @@ -629,11 +624,21 @@ def lenstronomy_kwargs(self, band=None): kwargs_model = { "lens_light_model_list": lens_light_model_list, "lens_model_list": lens_mass_model_list, - # source_redshift_list: redshifts for each source_light component - # z_source_convention: source redshift for convention of lens parameters (make sure LensCosmo has the same one) - # cosmo: astropy cosmology - # point_source_redshift_list: } + """"lens_redshift_list": [self.deflector_redshift]*len(lens_mass_model_list), + "z_lens": self.deflector_redshift, + "z_source": self.single_source_class.redshift, + "source_redshift_list" : self.source_redshift, + "z_source_convention" : self.single_source_class.redshift, + "cosmo": self.cosmo""" + if self.source_number > 1: + kwargs_model["lens_redshift_list"] = [ + self.deflector_redshift]*len(lens_mass_model_list) + kwargs_model["z_lens"] = self.deflector_redshift + kwargs_model["z_source"] = self.single_source_class.redshift + kwargs_model["source_redshift_list"] = self.source_redshift + kwargs_model["z_source_convention"] = self.single_source_class.redshift + kwargs_model["cosmo"] = self.cosmo sources, sources_kwargs = self.source_light_model_lenstronomy(band=band) # ensure that only the models that exist are getting added to kwargs_model @@ -714,12 +719,6 @@ def source_light_model_lenstronomy(self, band=None): kwargs_source_list.append(source.kwargs_extended_source_light( draw_area=self.test_area, center_lens=self.deflector_position, band=band )[0]) - """if self.source_number == 1: - _source_models_list = [item[0] for item in source_models_list] - _kwargs_source_list = kwargs_source_list[0] - else: - _source_models_list = source_models_list - _kwargs_source_list = kwargs_source_list""" source_models["source_light_model_list"] = source_models_list kwargs_source = kwargs_source_list else: From 86e10d84e452f19cbe98f9681a9ee44332f3b685 Mon Sep 17 00:00:00 2001 From: narayan Date: Tue, 29 Oct 2024 18:01:21 -0400 Subject: [PATCH 45/87] adjusted test functions for lens class --- slsim/lens.py | 17 ++++++++++++----- tests/test_lens.py | 32 ++++++++++++++++---------------- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/slsim/lens.py b/slsim/lens.py index b8614f984..ef0040c3b 100644 --- a/slsim/lens.py +++ b/slsim/lens.py @@ -305,8 +305,11 @@ def external_convergence(self): :return: external convergence """ - _, _, kappa_ext = self.los_linear_distortions - return kappa_ext + kappa_list = [] + for los_linear_dist in self.los_linear_distortions: + _, _, kappa_ext = los_linear_dist + kappa_list.append(kappa_ext) + return kappa_list @property def external_shear(self): @@ -314,8 +317,11 @@ def external_shear(self): :return: the absolute external shear """ - gamma1, gamma2, _ = self.los_linear_distortions - return (gamma1**2 + gamma2**2) ** 0.5 + shear_list = [] + for los_linear_dist in self.los_linear_distortions: + gamma1, gamma2, _ = los_linear_dist + shear_list.append((gamma1**2 + gamma2**2) ** 0.5) + return shear_list @property def einstein_radius_deflector(self): @@ -486,7 +492,8 @@ def point_source_magnitude(self, band, lensed=False, time=None): if lensed: magnif_list = self.point_source_magnification() - magnif_log_list = 2.5 * np.log10(abs(magnif_list)) + abs_magnif_list = [abs(i) for i in magnif_list] + magnif_log_list = 2.5 * np.log10(abs_magnif_list) #loop through all the source magnitude_list = [] for index, (source, magnif_log) in enumerate(zip( diff --git a/tests/test_lens.py b/tests/test_lens.py index 7f189f4f2..b030e1f80 100644 --- a/tests/test_lens.py +++ b/tests/test_lens.py @@ -85,7 +85,7 @@ def test_source_magnitude(self): assert source_magnitude_lensed == expected_lensed_mag def test_image_separation_from_positions(self): - image_positions = self.gg_lens.extended_source_image_positions() + image_positions = self.gg_lens.extended_source_image_positions()[0] image_separation = image_separation_from_positions(image_positions) theta_E_infinity = theta_e_when_source_infinity( deflector_dict=self.deflector_dict @@ -100,7 +100,7 @@ def test_theta_e_when_source_infinity(self): assert theta_E_infinity < 15 def test_extended_source_magnification(self): - host_mag = self.gg_lens.extended_source_magnification() + host_mag = self.gg_lens.extended_source_magnification()[0] assert host_mag > 0 def test_deflector_stellar_mass(self): @@ -125,7 +125,7 @@ def test_image_observer_times(self): t_obs2 = np.array([100, 200, 300]) dt_days = self.gg_lens.image_observer_times(t_obs=t_obs) dt_days2 = self.gg_lens.image_observer_times(t_obs=t_obs2) - arrival_times = self.gg_lens.point_source_arrival_times() + arrival_times = self.gg_lens.point_source_arrival_times()[0] observer_times = (t_obs - arrival_times + np.min(arrival_times))[:, np.newaxis] observer_times2 = ( t_obs2[:, np.newaxis] - arrival_times + np.min(arrival_times) @@ -291,8 +291,8 @@ def pes_lens_instance(): def test_point_source_magnitude(pes_lens_instance): pes_lens = pes_lens_instance - mag = pes_lens.point_source_magnitude(band="i", lensed=True) - mag_unlensed = pes_lens.point_source_magnitude(band="i") + mag = pes_lens.point_source_magnitude(band="i", lensed=True)[0] + mag_unlensed = pes_lens.point_source_magnitude(band="i")[0] assert len(mag) >= 2 assert len(mag_unlensed) == 1 @@ -334,8 +334,8 @@ def supernovae_lens_instance(): def test_point_source_magnitude_with_lightcurve(supernovae_lens_instance): supernovae_lens = supernovae_lens_instance - mag = supernovae_lens.point_source_magnitude(band="r", lensed=True) - expected_results = supernovae_lens_instance.source.source_dict["ps_mag_r"] + mag = supernovae_lens.point_source_magnitude(band="r", lensed=True)[0] + expected_results = supernovae_lens_instance.source[0].source_dict["ps_mag_r"] assert mag[0][0] != expected_results[0][0] assert mag[1][0] != expected_results[0][0] @@ -379,9 +379,9 @@ def test_different_setting(self): cosmo=self.cosmo, los_config=los1, ) - assert gg_lens.external_shear >= 0 - assert isinstance(gg_lens.external_convergence, float) - assert isinstance(gg_lens.external_shear, float) + assert gg_lens.external_shear[0] >= 0 + assert isinstance(gg_lens.external_convergence[0], float) + assert isinstance(gg_lens.external_shear[0], float) los2 = LOSConfig( los_bool=True, @@ -395,9 +395,9 @@ def test_different_setting(self): cosmo=self.cosmo, los_config=los2, ) - assert gg_lens_2.external_shear >= 0 - assert isinstance(gg_lens_2.external_convergence, float) - assert isinstance(gg_lens_2.external_shear, float) + assert gg_lens_2.external_shear[0] >= 0 + assert isinstance(gg_lens_2.external_convergence, list) + assert isinstance(gg_lens_2.external_shear, list) los3 = LOSConfig(los_bool=False) gg_lens_3 = Lens( @@ -406,8 +406,8 @@ def test_different_setting(self): cosmo=self.cosmo, los_config=los3, ) - assert gg_lens_3.external_convergence == 0 - assert gg_lens_3.external_shear == 0 + assert gg_lens_3.external_convergence[0] == 0 + assert gg_lens_3.external_shear[0] == 0 los4 = LOSConfig( los_bool=True, @@ -436,7 +436,7 @@ def test_image_number(self): los_config=los, ) image_number = gg_lens_number.image_number - assert (image_number == 4) or (image_number == 2) or (image_number == 1) + assert (image_number[0] == 4) or (image_number[0] == 2) or (image_number[0] == 1) if __name__ == "__main__": From e8b4d45160119518af1466e9f9d6121fe9bcc466 Mon Sep 17 00:00:00 2001 From: narayan Date: Tue, 29 Oct 2024 18:07:32 -0400 Subject: [PATCH 46/87] adjusted test functions for lens class --- tests/test_lens.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/test_lens.py b/tests/test_lens.py index b030e1f80..bead8df50 100644 --- a/tests/test_lens.py +++ b/tests/test_lens.py @@ -438,6 +438,13 @@ def test_image_number(self): image_number = gg_lens_number.image_number assert (image_number[0] == 4) or (image_number[0] == 2) or (image_number[0] == 1) + gg_lens_multisource = Lens( + source_class=[self.source6, self.source6], + deflector_class=self.deflector6, + cosmo=self.cosmo, + los_config=los, + ) + if __name__ == "__main__": pytest.main() From 6696a30390861c9e7fa9043be103587ef276f153 Mon Sep 17 00:00:00 2001 From: narayan Date: Tue, 29 Oct 2024 18:20:14 -0400 Subject: [PATCH 47/87] adjusted test functions for lens class --- tests/test_lens.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/test_lens.py b/tests/test_lens.py index bead8df50..ae5d31ee2 100644 --- a/tests/test_lens.py +++ b/tests/test_lens.py @@ -444,6 +444,24 @@ def test_image_number(self): cosmo=self.cosmo, los_config=los, ) + kwargs_model = gg_lens_multisource.lenstronomy_kwargs()[0] + kwargs_model_keys = kwargs_model.keys() + expected_kwargs_model = ['lens_light_model_list', + 'lens_model_list', + 'z_lens', + 'lens_redshift_list', + 'source_redshift_list', + 'z_source_convention', + 'cosmo', + 'source_light_model_list'] + assert expected_kwargs_model[0] in kwargs_model_keys + assert expected_kwargs_model[1] in kwargs_model_keys + assert expected_kwargs_model[2] in kwargs_model_keys + assert expected_kwargs_model[3] in kwargs_model_keys + assert expected_kwargs_model[4] in kwargs_model_keys + assert expected_kwargs_model[5] in kwargs_model_keys + assert expected_kwargs_model[6] in kwargs_model_keys + if __name__ == "__main__": From a801b26c1cba2ee54c1f4fae2e0c0bb1df639fc4 Mon Sep 17 00:00:00 2001 From: narayan Date: Tue, 29 Oct 2024 18:21:44 -0400 Subject: [PATCH 48/87] minor change --- tests/test_lens.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_lens.py b/tests/test_lens.py index ae5d31ee2..a782401c8 100644 --- a/tests/test_lens.py +++ b/tests/test_lens.py @@ -462,7 +462,5 @@ def test_image_number(self): assert expected_kwargs_model[5] in kwargs_model_keys assert expected_kwargs_model[6] in kwargs_model_keys - - if __name__ == "__main__": pytest.main() From b05ee2bd206409acf6d0a22deccb4fcb35021b09 Mon Sep 17 00:00:00 2001 From: narayan Date: Tue, 29 Oct 2024 18:26:29 -0400 Subject: [PATCH 49/87] minor change --- slsim/lensed_system_base.py | 71 ++++--------------------------------- 1 file changed, 6 insertions(+), 65 deletions(-) diff --git a/slsim/lensed_system_base.py b/slsim/lensed_system_base.py index ad16f8667..2a0818268 100644 --- a/slsim/lensed_system_base.py +++ b/slsim/lensed_system_base.py @@ -1,75 +1,16 @@ from abc import ABC, abstractmethod import numpy as np +from slsim.Sources.source import Source +from slsim.Deflectors.deflector import Deflector + class LensedSystemBase(ABC): """Abstract Base class to create a lens system with all lensing properties required to render populations.""" - def __init__( - self, - source_dict, - deflector_dict, - cosmo, - deflector_type="EPL", - test_area=4 * np.pi, - variability_model=None, - kwargs_variability=None, - sn_type=None, - sn_absolute_mag_band=None, - sn_absolute_zpsys=None, - lightcurve_time=None, - sn_modeldir=None, - agn_driving_variability_model=None, - agn_driving_kwargs_variability=None, - source_type="extended", - light_profile="single_sersic", - ): - """ - :param source_dict: source properties - :type source_dict: dict or astropy table - :param deflector_dict: deflector properties - :type deflector_dict: dict - :param deflector_type: type of deflector, i.e. "EPL", "NFW_HERNQUIST" - :type deflector_type: str - :param variability_model: keyword for variability model to be used. This is an - input for the Variability class. - :type variability_model: str - :param kwargs_variability: keyword arguments for the variability of a source. - This is associated with an input for Variability class. - :param sn_type: Supernova type (Ia, Ib, Ic, IIP, etc.) - :type sn_type: str - :param sn_absolute_mag_band: Band used to normalize to absolute magnitude - :type sn_absolute_mag_band: str or `~sncosmo.Bandpass` - :param sn_absolute_zpsys: Optional, AB or Vega (AB default) - :type sn_absolute_zpsys: str - :param cosmo: astropy.cosmology instance - :param test_area: area (arc-sec^2) around lensing galaxy to be investigated - :param lightcurve_time: observation time array for lightcurve in unit of days. - :type lightcurve_time: array - :param sn_modeldir: sn_modeldir is the path to the directory containing files - needed to initialize the sncosmo.model class. For example, - sn_modeldir = 'C:/Users/username/Documents/SALT3.NIR_WAVEEXT'. These data can - be downloaded from https://github.com/LSST-strong-lensing/data_public . - For more detail, please look at the documentation of RandomizedSupernovae - class. - :type sn_modeldir: str - :param agn_driving_variability_model: Variability model with light_curve output - which drives the variability across all bands of the agn. - :type agn_driving_variability_model: str (e.g. "light_curve", "sinusoidal", "bending_power_law") - :param agn_driving_kwargs_variability: Dictionary containing agn variability - parameters for the driving variability class. eg: variable_agn_kwarg_dict = - {"length_of_light_curve": 1000, "time_resolution": 1, - "log_breakpoint_frequency": 1 / 20, "low_frequency_slope": 1, - "high_frequency_slope": 3, "normal_magnitude_variance": 0.1}. For the detailed - explanation of these parameters, see generate_signal() function in - astro_util.py. - :type agn_driving_kwargs_variability: dict - :param source_type: type of the source 'extended' or 'point_source' or - 'point_plus_extended' supported - :type source_type: str - :param light_profile: keyword for number of sersic profile to use in source - light model - :type light_profile: str . Either "single_sersic" or "double_sersic" . + def __init__(self): + """ + # list input parameter """ @abstractmethod From 647832d35b8c0122238c0a11966e560095397b9d Mon Sep 17 00:00:00 2001 From: narayan Date: Tue, 29 Oct 2024 18:29:42 -0400 Subject: [PATCH 50/87] minor change --- slsim/lensed_system_base.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/slsim/lensed_system_base.py b/slsim/lensed_system_base.py index 2a0818268..f09172b98 100644 --- a/slsim/lensed_system_base.py +++ b/slsim/lensed_system_base.py @@ -1,18 +1,22 @@ from abc import ABC, abstractmethod -import numpy as np -from slsim.Sources.source import Source -from slsim.Deflectors.deflector import Deflector - class LensedSystemBase(ABC): """Abstract Base class to create a lens system with all lensing properties required to render populations.""" - def __init__(self): + def __init__(self, source_class, deflector_class): """ - # list input parameter + :param source_class: :param source_class: A Source class instance or list of + Source class instance + :type source_class: Source class instance from slsim.Sources.source. + :param deflector_class: deflector instance + :type deflector_class: Deflector class instance from slsim.Deflectors.deflector """ - + self.deflector = deflector_class + if isinstance(source_class, list): + self.source = source_class + else: + self.source = [source_class] @abstractmethod def deflector_position(self): """Center of the deflector position. From 920fbe9abe4a177b6fafa8996d451db9dbae96cc Mon Sep 17 00:00:00 2001 From: narayan Date: Tue, 29 Oct 2024 21:24:57 -0400 Subject: [PATCH 51/87] adjusted image_simulation module with Lens class changes. --- slsim/image_simulation.py | 69 +++++++++++++++++----------------- tests/test_image_simulation.py | 31 ++++++++++++--- 2 files changed, 61 insertions(+), 39 deletions(-) diff --git a/slsim/image_simulation.py b/slsim/image_simulation.py index 110809daf..6318b7741 100644 --- a/slsim/image_simulation.py +++ b/slsim/image_simulation.py @@ -264,18 +264,24 @@ def point_source_coordinate_properties( lens_pix_coordinate = image_data.map_coord2pix(ra_lens_value, dec_lens_value) ps_coordinate = lens_class.point_source_image_positions() - ra_image_values = ps_coordinate[0] - dec_image_values = ps_coordinate[1] - # image_magnitude = lens_class.point_source_magnitude(band=band, lensed=True) + ra_image = [] + dec_image = [] image_pix_coordinate = [] - for image_ra, image_dec in zip(ra_image_values, dec_image_values): - image_pix_coordinate.append((image_data.map_coord2pix(image_ra, image_dec))) + for ps_coord in ps_coordinate: + ra_image_values = ps_coord[0] + dec_image_values = ps_coord[1] + ra_image.append(ra_image_values) + dec_image.append(dec_image_values) + # image_magnitude = lens_class.point_source_magnitude(band=band, lensed=True) + for image_ra, image_dec in zip(ra_image_values, dec_image_values): + image_pix_coordinate.append(np.array([image_data.map_coord2pix(image_ra, + image_dec)])) data = { "deflector_pix": np.array(lens_pix_coordinate), - "image_pix": np.array(image_pix_coordinate), - "ra_image": ra_image_values, - "dec_image": dec_image_values, + "image_pix": image_pix_coordinate, + "ra_image": np.concatenate(ra_image), + "dec_image": np.concatenate(dec_image), } return data @@ -322,19 +328,16 @@ def point_source_image_without_variability( ra_image_values = image_data["ra_image"] dec_image_values = image_data["dec_image"] magnitude = lens_class.point_source_magnitude(band, lensed=True) - amp = magnitude_to_amplitude(magnitude, mag_zero_point) - point_source_images_list = [] - for i in range(len(ra_image_values)): - rendering_class = PointSourceRendering( - pixel_grid=data_class, supersampling_factor=1, psf=psf_class - ) - point_source = rendering_class.point_source_rendering( - np.array([ra_image_values[i]]), - np.array([dec_image_values[i]]), - np.array([amp[i]]), - ) - point_source_images_list.append(point_source) - point_source_image = sum(point_source_images_list) + magnitude_list = np.concatenate(magnitude) + amp = magnitude_to_amplitude(magnitude_list, mag_zero_point) + rendering_class = PointSourceRendering( + pixel_grid=data_class, supersampling_factor=1, psf=psf_class + ) + point_source_image = rendering_class.point_source_rendering( + ra_image_values, + dec_image_values, + amp, + ) else: point_source_image = np.zeros((num_pix, num_pix)) return point_source_image @@ -387,19 +390,17 @@ def point_source_image_at_time( variable_mag = lens_class.point_source_magnitude( band=band, lensed=True, time=time ) - variable_amp = magnitude_to_amplitude(variable_mag, mag_zero_point) - point_source_images_list = [] - for i in range(len(ra_image_values)): - rendering_class = PointSourceRendering( - pixel_grid=data_class, supersampling_factor=1, psf=psf_class - ) - point_source = rendering_class.point_source_rendering( - np.array([ra_image_values[i]]), - np.array([dec_image_values[i]]), - np.array([variable_amp[i]]), - ) - point_source_images_list.append(point_source) - point_source_image = sum(point_source_images_list) + variable_mag_list = np.concatenate(variable_mag) + variable_amp = magnitude_to_amplitude(variable_mag_list, mag_zero_point) + + rendering_class = PointSourceRendering( + pixel_grid=data_class, supersampling_factor=1, psf=psf_class + ) + point_source_image = rendering_class.point_source_rendering( + ra_image_values, + dec_image_values, + variable_amp, + ) else: point_source_image = np.zeros((num_pix, num_pix)) return point_source_image diff --git a/tests/test_image_simulation.py b/tests/test_image_simulation.py index 6f1c6b3aa..198fbd255 100644 --- a/tests/test_image_simulation.py +++ b/tests/test_image_simulation.py @@ -21,6 +21,8 @@ lens_image, lens_image_series, ) +from slsim.Sources.source import Source +from slsim.Deflectors.deflector import Deflector import pytest @@ -41,9 +43,19 @@ def setup_method(self): self.source_dict = blue_one self.deflector_dict = red_one while True: - gg_lens = Lens( - source_dict=self.source_dict, + self.source = Source( + source_dict=self.source_dict, + cosmo=cosmo, + source_type="extended", + light_profile="single_sersic", + ) + self.deflector = Deflector( + deflector_type="EPL", deflector_dict=self.deflector_dict, + ) + gg_lens = Lens( + source_class=self.source, + deflector_class=self.deflector, cosmo=cosmo, ) if gg_lens.validity_test(): @@ -169,12 +181,21 @@ def pes_lens_instance(): cosmo = FlatLambdaCDM(H0=70, Om0=0.3) while True: - pes_lens = Lens( + source = Source( source_dict=source_dict, - deflector_dict=deflector_dict, + cosmo=cosmo, source_type="point_plus_extended", + light_profile="single_sersic", variability_model="sinusoidal", - kwargs_variability={"amp", "freq"}, + kwargs_variability={"amp", "freq"} + ) + deflector = Deflector( + deflector_type="EPL", + deflector_dict=deflector_dict, + ) + pes_lens = Lens( + source_class=source, + deflector_class=deflector, cosmo=cosmo, ) if pes_lens.validity_test(): From 3ffbaf831e07e304a593a48bd0c0c5e21aeed4cc Mon Sep 17 00:00:00 2001 From: narayan Date: Tue, 29 Oct 2024 21:38:02 -0400 Subject: [PATCH 52/87] adjusted some test functions with recent Lens class changes. --- tests/test_Plots/test_plot_functions.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/tests/test_Plots/test_plot_functions.py b/tests/test_Plots/test_plot_functions.py index af0044a7a..41e5f2e72 100644 --- a/tests/test_Plots/test_plot_functions.py +++ b/tests/test_Plots/test_plot_functions.py @@ -9,6 +9,8 @@ plot_montage_of_random_injected_lens, ) from slsim.image_simulation import sharp_image +from slsim.Sources.source import Source +from slsim.Deflectors.deflector import Deflector from astropy.table import Table import os @@ -26,12 +28,21 @@ def quasar_lens_pop_instance(): cosmo = FlatLambdaCDM(H0=70, Om0=0.3) while True: - pes_lens = Lens( + source = Source( source_dict=source_dict, - deflector_dict=deflector_dict, + cosmo=cosmo, source_type="point_plus_extended", + light_profile="single_sersic", variability_model="sinusoidal", kwargs_variability={"amp", "freq"}, + ) + deflector = Deflector( + deflector_type="EPL", + deflector_dict=deflector_dict, + ) + pes_lens = Lens( + source_class=source, + deflector_class=deflector, cosmo=cosmo, ) if pes_lens.validity_test(): From 021631d5561e7df84f61c049df13cee8c29b9846 Mon Sep 17 00:00:00 2001 From: narayan Date: Tue, 29 Oct 2024 21:51:40 -0400 Subject: [PATCH 53/87] adjusted some test functions with recent Lens class changes. --- tests/test_roman_image_simulation.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/tests/test_roman_image_simulation.py b/tests/test_roman_image_simulation.py index 233262493..8526d34f3 100644 --- a/tests/test_roman_image_simulation.py +++ b/tests/test_roman_image_simulation.py @@ -3,7 +3,8 @@ from slsim.lens import Lens from slsim.roman_image_simulation import simulate_roman_image from slsim.image_simulation import simulate_image - +from slsim.Sources.source import Source +from slsim.Deflectors.deflector import Deflector import os import pickle import pytest @@ -46,10 +47,19 @@ } BAND = "F106" - +source = Source( + source_dict=SOURCE_DICT, + cosmo=COSMO, + source_type="extended", + light_profile="single_sersic", + ) +deflector = Deflector( + deflector_type="EPL", + deflector_dict=DEFLECTOR_DICT, + ) LENS = Lens( - source_dict=SOURCE_DICT, - deflector_dict=DEFLECTOR_DICT, + source_class=source, + deflector_class=deflector, los_dict=LOS_DICT, cosmo=COSMO, ) From 0bed18b60b6c327d3cd0d0cc53e65417a8bdad34 Mon Sep 17 00:00:00 2001 From: narayan Date: Wed, 30 Oct 2024 15:10:26 -0400 Subject: [PATCH 54/87] minor change --- slsim/lens.py | 141 ++++++++++++++++++----------------------- tests/test_lens_pop.py | 8 +-- 2 files changed, 64 insertions(+), 85 deletions(-) diff --git a/slsim/lens.py b/slsim/lens.py index ef0040c3b..62711c36b 100644 --- a/slsim/lens.py +++ b/slsim/lens.py @@ -189,23 +189,6 @@ def validity_test( min_image_separation=0, max_image_separation=10, mag_arc_limit=None, - ): - """Check if the lensing configuration is valid for each source.""" - for source, einstein_radius, image_pos in zip(self.source, self.einstein_radius, - self.point_source_image_positions()): - if not self._validity_test(source, einstein_radius, image_pos, - min_image_separation, max_image_separation, mag_arc_limit): - return False - return True - - def _validity_test( - self, - source, - einstein_radius, - image_positions, - min_image_separation=0, - max_image_separation=10, - mag_arc_limit=None, ): """Check whether lensing configuration matches selection and plausibility criteria. @@ -217,65 +200,67 @@ def _validity_test( :type mag_arc_limit: dict with key of bands and values of magnitude limits :return: boolean """ - # Criteria 1:The redshift of the lens (z_lens) must be less than the - # redshift of the source (z_source). - z_lens = self.deflector.redshift - z_source = source.redshift - if z_lens >= z_source: - return False - - # Criteria 2: The angular Einstein radius of the lensing configuration (theta_E) - # times 2 must be greater than or equal to the minimum image separation - # (min_image_separation) and less than or equal to the maximum image - # separation (max_image_separation). - if not min_image_separation <= 2 * einstein_radius <= max_image_separation: - return False - - # Criteria 3: The distance between the lens center and the source position - # must be less than or equal to the angular Einstein radius - # of the lensing configuration (times sqrt(2)). - center_lens, center_source = ( - self.deflector_position, - source.point_source_position( - center_lens=self.deflector_position, draw_area=self.test_area - ), - ) - if np.sum((center_lens - center_source) ** 2) > einstein_radius**2 * 2: - return False - - # Criteria 4: The lensing configuration must produce at least two SL images. - image_positions = image_positions - if len(image_positions[0]) < 2: - return False - - # Criteria 5: The maximum separation between any two image positions must be - # greater than or equal to the minimum image separation and less than or - # equal to the maximum image separation. - image_separation = image_separation_from_positions(image_positions) - if not min_image_separation <= image_separation <= max_image_separation: - return False - - # Criteria 6: (optional) - # compute the magnified brightness of the lensed extended arc for different - # bands at least in one band, the magnitude has to be brighter than the limit - if mag_arc_limit is not None and self._source_type in [ - "extended", - "point_plus_extended", - ]: - # makes sure magnification of extended source is only used when there is - # an extended source - bool_mag_limit = False - host_mag = self.extended_source_magnification() - for band, mag_limit_band in mag_arc_limit.items(): - mag_source = self.extended_source_magnitude(band) - mag_arc = mag_source - 2.5 * np.log10( - host_mag - ) # lensing magnification results in a shift in magnitude - if mag_arc < mag_limit_band: - bool_mag_limit = True - break - if bool_mag_limit is False: + for index, (source, einstein_radius) in enumerate(zip( + self.source, self.einstein_radius)): + # Criteria 1:The redshift of the lens (z_lens) must be less than the + # redshift of the source (z_source). + z_lens = self.deflector.redshift + z_source = source.redshift + if z_lens >= z_source: + return False + + # Criteria 2: The angular Einstein radius of the lensing configuration (theta_E) + # times 2 must be greater than or equal to the minimum image separation + # (min_image_separation) and less than or equal to the maximum image + # separation (max_image_separation). + if not min_image_separation <= 2 * einstein_radius <= max_image_separation: + return False + + # Criteria 3: The distance between the lens center and the source position + # must be less than or equal to the angular Einstein radius + # of the lensing configuration (times sqrt(2)). + center_lens, center_source = ( + self.deflector_position, + source.point_source_position( + center_lens=self.deflector_position, draw_area=self.test_area + ), + ) + if np.sum((center_lens - center_source) ** 2) > einstein_radius**2 * 2: return False + + # Criteria 4: The lensing configuration must produce at least two SL images. + image_positions = self.point_source_image_positions()[index] + if len(image_positions[0]) < 2: + return False + + # Criteria 5: The maximum separation between any two image positions must be + # greater than or equal to the minimum image separation and less than or + # equal to the maximum image separation. + image_separation = image_separation_from_positions(image_positions) + if not min_image_separation <= image_separation <= max_image_separation: + return False + + # Criteria 6: (optional) + # compute the magnified brightness of the lensed extended arc for different + # bands at least in one band, the magnitude has to be brighter than the limit + if mag_arc_limit is not None and self._source_type in [ + "extended", + "point_plus_extended", + ]: + # makes sure magnification of extended source is only used when there is + # an extended source + bool_mag_limit = False + host_mag = self.extended_source_magnification()[index] + for band, mag_limit_band in mag_arc_limit.items(): + mag_source = self.extended_source_magnitude(band)[index] + mag_arc = mag_source - 2.5 * np.log10( + host_mag + ) # lensing magnification results in a shift in magnitude + if mag_arc < mag_limit_band: + bool_mag_limit = True + break + if bool_mag_limit is False: + return False # TODO make similar criteria for point source magnitudes return True # TODO: test for signal-to-noise ratio in surface brightness @@ -632,12 +617,6 @@ def lenstronomy_kwargs(self, band=None): "lens_light_model_list": lens_light_model_list, "lens_model_list": lens_mass_model_list, } - """"lens_redshift_list": [self.deflector_redshift]*len(lens_mass_model_list), - "z_lens": self.deflector_redshift, - "z_source": self.single_source_class.redshift, - "source_redshift_list" : self.source_redshift, - "z_source_convention" : self.single_source_class.redshift, - "cosmo": self.cosmo""" if self.source_number > 1: kwargs_model["lens_redshift_list"] = [ self.deflector_redshift]*len(lens_mass_model_list) diff --git a/tests/test_lens_pop.py b/tests/test_lens_pop.py index 9fde452ab..ed679ba38 100644 --- a/tests/test_lens_pop.py +++ b/tests/test_lens_pop.py @@ -294,9 +294,9 @@ def test_supernovae_plus_galaxies_lens_pop_instance_2(): kwargs_lens_cut = {} pes_lens_class = pes_lens_pop.select_lens_at_random(**kwargs_lens_cut) assert pes_lens_class._source_type == "point_plus_extended" - assert "x_off" in pes_lens_class.source.source_dict.colnames + assert "x_off" in pes_lens_class.source[0].source_dict.colnames assert len( - pes_lens_class.source.kwargs_variability_extracted["i"]["ps_mag_i"] + pes_lens_class.source[0].kwargs_variability_extracted["i"]["ps_mag_i"] ) == len(time_range) @@ -363,8 +363,8 @@ def test_supernovae_lens_pop_instance(): kwargs_lens_cut = {} ps_lens_class = ps_lens_pop_1.select_lens_at_random(**kwargs_lens_cut) assert ps_lens_class._source_type == "point_source" - assert "z" in ps_lens_class.source.source_dict.colnames - assert len(ps_lens_class.source.source_dict) == 1 + assert "z" in ps_lens_class.source[0].source_dict.colnames + assert len(ps_lens_class.source[0].source_dict) == 1 assert abs(len(ps_lens_population_1) - len(ps_lens_population_1_speed)) <= 12 with pytest.raises(ValueError): LensPop( From 8f61daea9aed043aa83c9fb8ee39a05ae7f431f0 Mon Sep 17 00:00:00 2001 From: narayan Date: Wed, 30 Oct 2024 15:26:18 -0400 Subject: [PATCH 55/87] minor change --- tests/test_coolest_slsim_interface.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/tests/test_coolest_slsim_interface.py b/tests/test_coolest_slsim_interface.py index 1a45bd1f2..77215aa34 100644 --- a/tests/test_coolest_slsim_interface.py +++ b/tests/test_coolest_slsim_interface.py @@ -4,6 +4,8 @@ create_slsim_from_coolest, ) from slsim.lens import Lens +from slsim.Sources.source import Source +from slsim.Deflectors.deflector import Deflector import os from astropy.cosmology import FlatLambdaCDM from astropy.table import Table @@ -23,18 +25,27 @@ def supernovae_lens_instance(): cosmo = FlatLambdaCDM(H0=70, Om0=0.3) while True: - supernovae_lens = Lens( - deflector_dict=deflector_dict, + source = Source( source_dict=source_dict, + cosmo=cosmo, + source_type="point_plus_extended", + light_profile="double_sersic", + lightcurve_time=np.linspace(-20, 100, 1000), variability_model="light_curve", kwargs_variability={"supernovae_lightcurve", "i"}, sn_type="Ia", sn_absolute_mag_band="bessellb", sn_absolute_zpsys="ab", + + ) + deflector = Deflector( + deflector_type="EPL", + deflector_dict=deflector_dict, + ) + supernovae_lens = Lens( + deflector_class=deflector, + source_class=source, cosmo=cosmo, - source_type="point_plus_extended", - light_profile="double_sersic", - lightcurve_time=np.linspace(-20, 100, 1000), ) if supernovae_lens.validity_test(): supernovae_lens = supernovae_lens From 542c987c91dab64970b7d5b6b18b92c596095e91 Mon Sep 17 00:00:00 2001 From: narayan Date: Wed, 30 Oct 2024 16:45:33 -0400 Subject: [PATCH 56/87] adjusted some of the test functions with recent change. --- .../lsst_science_pipeline.py | 4 ++-- slsim/lens.py | 18 ++++++++++++++---- .../test_lsst_science_pipeline.py | 17 ++++++++++++++--- tests/test_Source/test_galaxies.py | 4 ---- 4 files changed, 30 insertions(+), 13 deletions(-) diff --git a/slsim/LsstSciencePipeline/lsst_science_pipeline.py b/slsim/LsstSciencePipeline/lsst_science_pipeline.py index c968f3b99..ca003185e 100644 --- a/slsim/LsstSciencePipeline/lsst_science_pipeline.py +++ b/slsim/LsstSciencePipeline/lsst_science_pipeline.py @@ -826,8 +826,8 @@ def variable_lens_injection( # lightcurve time. So, we use random observation time from the lens class lightcurve # time to ensure simulation of reasonable images. observation_time = np.random.uniform( - min(lens_class.source.lightcurve_time), - max(lens_class.source.lightcurve_time), + min(lens_class.single_source_class.lightcurve_time), + max(lens_class.single_source_class.lightcurve_time), size=len(exposure_data["obs_time"]), ) observation_time.sort() diff --git a/slsim/lens.py b/slsim/lens.py index 62711c36b..67b60d66c 100644 --- a/slsim/lens.py +++ b/slsim/lens.py @@ -701,12 +701,22 @@ def source_light_model_lenstronomy(self, band=None): source_models_list = [] kwargs_source_list = [] for source in self.source: - source_models_list.append(source.extended_source_light_model()[0]) + source_models_list.append(source.extended_source_light_model()) kwargs_source_list.append(source.kwargs_extended_source_light( draw_area=self.test_area, center_lens=self.deflector_position, band=band - )[0]) - source_models["source_light_model_list"] = source_models_list - kwargs_source = kwargs_source_list + )) + #lets transform list in to required structure + if (self.single_source_class.light_profile == "double_sersic" and + self.source_number > 1): + source_models_list_restructure = source_models_list + kwargs_source_list_restructure = kwargs_source_list + else: + source_models_list_restructure = list( + np.concatenate(source_models_list)) + kwargs_source_list_restructure = list( + np.concatenate(kwargs_source_list)) + source_models["source_light_model_list"] = source_models_list_restructure + kwargs_source = kwargs_source_list_restructure else: # source_models['source_light_model_list'] = None kwargs_source = None diff --git a/tests/test_LsstSciencePipeline/test_lsst_science_pipeline.py b/tests/test_LsstSciencePipeline/test_lsst_science_pipeline.py index 6132081db..838b945df 100644 --- a/tests/test_LsstSciencePipeline/test_lsst_science_pipeline.py +++ b/tests/test_LsstSciencePipeline/test_lsst_science_pipeline.py @@ -7,6 +7,8 @@ variable_lens_injection, multiple_variable_lens_injection, ) +from slsim.Sources.source import Source +from slsim.Deflectors.deflector import Deflector import pytest @@ -22,15 +24,24 @@ def pes_lens_instance(): cosmo = FlatLambdaCDM(H0=70, Om0=0.3) while True: - pes_lens = Lens( + source = Source( source_dict=source_dict, - deflector_dict=deflector_dict, + cosmo=cosmo, source_type="point_plus_extended", + light_profile="single_sersic", variability_model="sinusoidal", kwargs_variability={"amp", "freq"}, - cosmo=cosmo, lightcurve_time=np.linspace(0, np.pi, 100), ) + deflector = Deflector( + deflector_type="EPL", + deflector_dict=deflector_dict, + ) + pes_lens = Lens( + source_class=source, + deflector_class=deflector, + cosmo=cosmo, + ) if pes_lens.validity_test(): pes_lens = pes_lens break diff --git a/tests/test_Source/test_galaxies.py b/tests/test_Source/test_galaxies.py index 5c9bad6c4..ee7229cb6 100644 --- a/tests/test_Source/test_galaxies.py +++ b/tests/test_Source/test_galaxies.py @@ -283,14 +283,10 @@ def test_source_number(self): def test_draw_source(self): galaxy = self.galaxies.draw_source() galaxy_1 = self.galaxies4.draw_source() - galaxy_2 = self.galaxies.draw_source(z_max=1) - galaxy_3 = self.galaxies5.draw_source(z_max=0.4) assert len(galaxy) > 0 assert galaxy_1["n_sersic"] == 1 - assert galaxy_2["z"] < 1 + 0.002 with pytest.raises(ValueError): self.galaxies5.draw_source() - assert galaxy_3 is None def test_draw_source_double_sersic(self): galaxy1 = self.galaxies2.draw_source() From b556dfe1206143d176988d1989ec19750555ba41 Mon Sep 17 00:00:00 2001 From: narayan Date: Wed, 30 Oct 2024 16:47:48 -0400 Subject: [PATCH 57/87] adjusted some of the test functions with recent change. --- .../test_opsim_pipeline.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/tests/test_LsstSciencePipeline/test_opsim_pipeline.py b/tests/test_LsstSciencePipeline/test_opsim_pipeline.py index df4418ecd..d84829101 100644 --- a/tests/test_LsstSciencePipeline/test_opsim_pipeline.py +++ b/tests/test_LsstSciencePipeline/test_opsim_pipeline.py @@ -7,6 +7,8 @@ opsim_variable_lens_injection, opsim_time_series_images_data, ) +from slsim.Sources.source import Source +from slsim.Deflectors.deflector import Deflector import pytest @@ -22,15 +24,24 @@ def pes_lens_instance(): cosmo = FlatLambdaCDM(H0=70, Om0=0.3) while True: - pes_lens = Lens( + source = Source( source_dict=source_dict, - deflector_dict=deflector_dict, + cosmo=cosmo, source_type="point_plus_extended", + light_profile="single_sersic", variability_model="sinusoidal", kwargs_variability={"amp", "freq"}, - cosmo=cosmo, lightcurve_time=np.linspace(0, np.pi, 100), ) + deflector = Deflector( + deflector_type="EPL", + deflector_dict=deflector_dict, + ) + pes_lens = Lens( + source_class=source, + deflector_class=deflector, + cosmo=cosmo, + ) if pes_lens.validity_test(): pes_lens = pes_lens break From e362011cc7e0fbabcb79c646623550c73d0e01b9 Mon Sep 17 00:00:00 2001 From: narayan Date: Wed, 30 Oct 2024 17:11:47 -0400 Subject: [PATCH 58/87] added some test functions. --- tests/test_lens.py | 48 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/tests/test_lens.py b/tests/test_lens.py index a782401c8..34b60bcc3 100644 --- a/tests/test_lens.py +++ b/tests/test_lens.py @@ -461,6 +461,54 @@ def test_image_number(self): assert expected_kwargs_model[4] in kwargs_model_keys assert expected_kwargs_model[5] in kwargs_model_keys assert expected_kwargs_model[6] in kwargs_model_keys + +@pytest.fixture +def supernovae_lens_instance_double_sersic_multisource(): + path = os.path.dirname(__file__) + source_dict = Table.read( + os.path.join(path, "TestData/source_supernovae_new.fits"), format="fits" + ) + deflector_dict = Table.read( + os.path.join(path, "TestData/deflector_supernovae_new.fits"), format="fits" + ) + + cosmo = FlatLambdaCDM(H0=70, Om0=0.3) + while True: + source = Source( + source_dict=source_dict, + cosmo=cosmo, + source_type="point_plus_extended", + light_profile="double_sersic", + lightcurve_time=np.linspace(-20, 100, 1000), + variability_model="light_curve", + kwargs_variability={"supernovae_lightcurve", "i"}, + sn_type="Ia", + sn_absolute_mag_band="bessellb", + sn_absolute_zpsys="ab", + + ) + deflector = Deflector( + deflector_type="EPL", + deflector_dict=deflector_dict, + ) + supernovae_lens = Lens( + deflector_class=deflector, + source_class=[source, source], + cosmo=cosmo, + ) + if supernovae_lens.validity_test(): + supernovae_lens = supernovae_lens + break + return supernovae_lens + +def test_double_sersic_multisource(supernovae_lens_instance_double_sersic_multisource): + lens_class = supernovae_lens_instance_double_sersic_multisource + results = lens_class.source_light_model_lenstronomy(band="i") + assert len(results[0]["source_light_model_list"]) == 2 + assert len(results[1]["kwargs_source"]) == 2 + assert len(results[1]["kwargs_ps"]) == 2 + + if __name__ == "__main__": pytest.main() From c31a23f4c555073400c5e0df6347a10742f5310f Mon Sep 17 00:00:00 2001 From: narayan Date: Wed, 30 Oct 2024 18:36:27 -0400 Subject: [PATCH 59/87] implemented multi source plane lensing in LensPop class. --- slsim/lens_pop.py | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/slsim/lens_pop.py b/slsim/lens_pop.py index 1aa8e2be9..b1fd2dedf 100644 --- a/slsim/lens_pop.py +++ b/slsim/lens_pop.py @@ -171,7 +171,7 @@ def draw_population(self, kwargs_lens_cuts, speed_factor=1): """ # Initialize an empty list to store the Lens instances - gg_lens_population = [] + lens_population = [] # Estimate the number of lensing systems num_lenses = self.deflector_number # num_sources = self._source_galaxies.galaxies_number() @@ -191,8 +191,8 @@ def draw_population(self, kwargs_lens_cuts, speed_factor=1): deflector_type=self._lens_galaxies.deflector_profile, deflector_dict=lens, ) - # TODO: to implement this for a multi-source plane lens system if num_sources_tested > 0: + valid_sources = [] n = 0 while n < num_sources_tested: source = self._sources.draw_source() @@ -211,7 +211,7 @@ def draw_population(self, kwargs_lens_cuts, speed_factor=1): source_type=self._sources.source_type, light_profile=self._sources.light_profile, ) - gg_lens = Lens( + lens_class = Lens( deflector_class=_lens, source_class=_source, cosmo=self.cosmo, @@ -219,16 +219,25 @@ def draw_population(self, kwargs_lens_cuts, speed_factor=1): los_config=self.los_config, ) # Check the validity of the lens system - if gg_lens.validity_test(**kwargs_lens_cuts): - gg_lens_population.append(gg_lens) - # if a lens system passes the validity test, code should exit - # the loop. so, n should be greater or equal to - # num_sources_tested which will break the while loop - # (instead of this one can simply use break). - n = num_sources_tested + if lens_class.validity_test(**kwargs_lens_cuts): + valid_sources.append(_source) + n += 1 + if valid_sources: + # Use a single source if only one source is valid, else use + # the list of valid sources + if len(valid_sources) == 1: + final_sources = valid_sources[0] else: - n += 1 - return gg_lens_population + final_sources = valid_sources + lens_final = Lens( + deflector_class=_lens, + source_class=final_sources, + cosmo=self.cosmo, + test_area=test_area, + los_config=self.los_config, + ) + lens_population.append(lens_final) + return lens_population def draw_test_area(deflector): From e1bf9e95681a58dce8922295c82058d72065a88a Mon Sep 17 00:00:00 2001 From: narayan Date: Thu, 31 Oct 2024 12:36:03 -0400 Subject: [PATCH 60/87] transfered some of the function of lsst_science_pipeline module to util_lsst.py --- .../lsst_science_pipeline.py | 127 +----------------- .../test_lsst_science_pipeline.py | 2 +- 2 files changed, 5 insertions(+), 124 deletions(-) diff --git a/slsim/LsstSciencePipeline/lsst_science_pipeline.py b/slsim/LsstSciencePipeline/lsst_science_pipeline.py index ca003185e..9faed8708 100644 --- a/slsim/LsstSciencePipeline/lsst_science_pipeline.py +++ b/slsim/LsstSciencePipeline/lsst_science_pipeline.py @@ -1,10 +1,8 @@ import numpy as np from astropy.table import Table, vstack -from astropy.table import Column from slsim.image_simulation import ( sharp_image, lens_image, - lens_image_series, ) from slsim.Util.param_util import transformmatrix_to_pixelscale from scipy import interpolate @@ -497,10 +495,11 @@ def cutout_image_psf_kernel( calibFluxRadius=12, ): """This function extracts psf kernels from the dp0 cutout image at point source - image positions and deflector position. In the dp0.2 data, psf kernel vary with - coordinate and can be computed using given psf model. + image positions and deflector position. dp0 images are objects that has various + attributes. In the dp0.2 data, psf kernel vary with coordinate and can be computed + using given psf model. - :param dp0_image: cutout image from the dp0 data or any other image + :param dp0_image: cutout image from the dp0 data. :param lens_class: class object containing all information of the lensing system (e.g., Lens()) :param band: imaging band @@ -798,124 +797,6 @@ def multiple_dp0_time_series_images_data( return expo_data_list return None - -def variable_lens_injection( - lens_class, band, num_pix, transform_pix2angle, exposure_data -): - """Injects variable lens to the dp0 time series data. - - :param lens_class: Lens() object - :param band: imaging band - :param num_pix: number of pixels per axis - :param transform_pix2angle: transformation matrix (2x2) of pixels into coordinate - displacements - :param exposure_data: An astropy table of exposure data. It must contain calexp - images or generated noisy background image (column name should be - "time_series_images", these images are single exposure images of the same part - of the sky at different time), magnitude zero point (column name should be - "zero_point", these are zero point magnitudes for each single exposure images in - time series image) , psf kernel for each exposure (column name should be - "psf_kernel", these are pixel psf kernel for each single exposure images in time - series image), exposure time (column name should be "expo_time", these are - exposure time for each single exposure images in time series images), - observation time (column name should be "obs_time", these are observation time - in days for each single exposure images in time series images) - :return: Astropy table of injected lenses and exposure information of dp0 data - """ - # the range of observation time of single exposure images might be outside of the - # lightcurve time. So, we use random observation time from the lens class lightcurve - # time to ensure simulation of reasonable images. - observation_time = np.random.uniform( - min(lens_class.single_source_class.lightcurve_time), - max(lens_class.single_source_class.lightcurve_time), - size=len(exposure_data["obs_time"]), - ) - observation_time.sort() - new_obs_time = Column(name="obs_time", data=observation_time) - exposure_data.replace_column("obs_time", new_obs_time) - lens_images = lens_image_series( - lens_class, - band=band, - mag_zero_point=exposure_data["zero_point"], - num_pix=num_pix, - psf_kernel=exposure_data["psf_kernel"], - transform_pix2angle=transform_pix2angle, - exposure_time=exposure_data["expo_time"], - t_obs=exposure_data["obs_time"], - ) - - final_image = [] - for i in range(len(exposure_data["obs_time"])): - final_image.append(exposure_data["time_series_images"][i] + lens_images[i]) - lens_col = Column(name="lens", data=lens_images) - final_image_col = Column(name="injected_lens", data=final_image) - if "lens" in exposure_data.colnames: - exposure_data.replace_column("lens", lens_col) - else: - exposure_data.add_column(lens_col) - if "injected_lens" in exposure_data.colnames: - exposure_data.replace_column("injected_lens", final_image_col) - else: - exposure_data.add_column(final_image_col) - return exposure_data - - -def multiple_variable_lens_injection( - lens_class_list, - band, - num_pix, - transform_matrices_list, - exposure_data_list, - output_file=None, -): - """Injects multiple variable lenses to multiple dp0 time series data. - - :param lens_class_list: list of Lens() object - :param band: imaging band - :param num_pix: number of pixels per axis - :param transform_matrices_list: list of transformation matrix (2x2) of pixels into - coordinate displacements for each exposure - :param exposure_data: list of astropy table of exposure data for each set of time - series images. It must contain calexp images or generated noisy background image - (column name should be "time_series_images", these images are single exposure - images of the same part of the sky at different time), magnitude zero point - (column name should be "zero_point", these are zero point magnitudes for each - single exposure images in time series image) , psf kernel for each exposure - (column name should be "psf_kernel", these are pixel psf kernel for each single - exposure images in time series image), exposure time (column name should be - "expo_time", these are exposure time for each single exposure images in time - series images), observation time (column name should be "obs_time", these are - observation time in days for each single exposure images in time series images) - :param output_file: path to the output FITS file where data will be saved - :return: list of astropy table of injected lenses and exposure information of dp0 - data for each time series lenses. If output_file path is provided, it saves list - of these astropy table in fits file with the given name. - """ - final_images_catalog = [] - for lens_class, transform_matrices, expo_data in zip( - lens_class_list, transform_matrices_list, exposure_data_list - ): - variable_injected_image = variable_lens_injection( - lens_class, - band=band, - num_pix=num_pix, - transform_pix2angle=transform_matrices, - exposure_data=expo_data, - ) - if output_file is None: - final_images_catalog.append(variable_injected_image) - else: - first_table = not os.path.exists(output_file) - if first_table: - variable_injected_image.write(output_file, overwrite=True) - first_table = False - else: - fits_append_table(output_file, variable_injected_image) - if len(final_images_catalog) > 1: - return final_images_catalog - return None - - def measure_noise_level_in_RSP_coadd(RSP_coadd, N_pixels, plot=False): np.random.seed(1) """Function to measure the noise level within a central square aperture of an RSP diff --git a/tests/test_LsstSciencePipeline/test_lsst_science_pipeline.py b/tests/test_LsstSciencePipeline/test_lsst_science_pipeline.py index 838b945df..5c0266387 100644 --- a/tests/test_LsstSciencePipeline/test_lsst_science_pipeline.py +++ b/tests/test_LsstSciencePipeline/test_lsst_science_pipeline.py @@ -3,7 +3,7 @@ from astropy.table import Table from astropy.cosmology import FlatLambdaCDM from slsim.lens import Lens -from slsim.LsstSciencePipeline.lsst_science_pipeline import ( +from slsim.LsstSciencePipeline.util_lsst import ( variable_lens_injection, multiple_variable_lens_injection, ) From eba268011b0fbf54fbd63c10157ecaac973f3cb2 Mon Sep 17 00:00:00 2001 From: narayan Date: Thu, 31 Oct 2024 12:37:03 -0400 Subject: [PATCH 61/87] added a util_lsst.py --- slsim/LsstSciencePipeline/util_lsst.py | 122 +++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 slsim/LsstSciencePipeline/util_lsst.py diff --git a/slsim/LsstSciencePipeline/util_lsst.py b/slsim/LsstSciencePipeline/util_lsst.py new file mode 100644 index 000000000..c7581f9e6 --- /dev/null +++ b/slsim/LsstSciencePipeline/util_lsst.py @@ -0,0 +1,122 @@ +import numpy as np +from astropy.table import Column +from slsim.image_simulation import lens_image_series +from slsim.Util.param_util import fits_append_table +import os + + +def variable_lens_injection( + lens_class, band, num_pix, transform_pix2angle, exposure_data +): + """Injects variable lens to the dp0 time series data. + + :param lens_class: Lens() object + :param band: imaging band + :param num_pix: number of pixels per axis + :param transform_pix2angle: transformation matrix (2x2) of pixels into coordinate + displacements + :param exposure_data: An astropy table of exposure data. It must contain calexp + images or generated noisy background image (column name should be + "time_series_images", these images are single exposure images of the same part + of the sky at different time), magnitude zero point (column name should be + "zero_point", these are zero point magnitudes for each single exposure images in + time series image) , psf kernel for each exposure (column name should be + "psf_kernel", these are pixel psf kernel for each single exposure images in time + series image), exposure time (column name should be "expo_time", these are + exposure time for each single exposure images in time series images), + observation time (column name should be "obs_time", these are observation time + in days for each single exposure images in time series images) + :return: Astropy table of injected lenses and exposure information of dp0 data + """ + # the range of observation time of single exposure images might be outside of the + # lightcurve time. So, we use random observation time from the lens class lightcurve + # time to ensure simulation of reasonable images. + observation_time = np.random.uniform( + min(lens_class.single_source_class.lightcurve_time), + max(lens_class.single_source_class.lightcurve_time), + size=len(exposure_data["obs_time"]), + ) + observation_time.sort() + new_obs_time = Column(name="obs_time", data=observation_time) + exposure_data.replace_column("obs_time", new_obs_time) + lens_images = lens_image_series( + lens_class, + band=band, + mag_zero_point=exposure_data["zero_point"], + num_pix=num_pix, + psf_kernel=exposure_data["psf_kernel"], + transform_pix2angle=transform_pix2angle, + exposure_time=exposure_data["expo_time"], + t_obs=exposure_data["obs_time"], + ) + + final_image = [] + for i in range(len(exposure_data["obs_time"])): + final_image.append(exposure_data["time_series_images"][i] + lens_images[i]) + lens_col = Column(name="lens", data=lens_images) + final_image_col = Column(name="injected_lens", data=final_image) + if "lens" in exposure_data.colnames: + exposure_data.replace_column("lens", lens_col) + else: + exposure_data.add_column(lens_col) + if "injected_lens" in exposure_data.colnames: + exposure_data.replace_column("injected_lens", final_image_col) + else: + exposure_data.add_column(final_image_col) + return exposure_data + + +def multiple_variable_lens_injection( + lens_class_list, + band, + num_pix, + transform_matrices_list, + exposure_data_list, + output_file=None, +): + """Injects multiple variable lenses to multiple dp0 time series data. + + :param lens_class_list: list of Lens() object + :param band: imaging band + :param num_pix: number of pixels per axis + :param transform_matrices_list: list of transformation matrix (2x2) of pixels into + coordinate displacements for each exposure + :param exposure_data: list of astropy table of exposure data for each set of time + series images. It must contain calexp images or generated noisy background image + (column name should be "time_series_images", these images are single exposure + images of the same part of the sky at different time), magnitude zero point + (column name should be "zero_point", these are zero point magnitudes for each + single exposure images in time series image) , psf kernel for each exposure + (column name should be "psf_kernel", these are pixel psf kernel for each single + exposure images in time series image), exposure time (column name should be + "expo_time", these are exposure time for each single exposure images in time + series images), observation time (column name should be "obs_time", these are + observation time in days for each single exposure images in time series images) + :param output_file: path to the output FITS file where data will be saved + :return: list of astropy table of injected lenses and exposure information of dp0 + data for each time series lenses. If output_file path is provided, it saves list + of these astropy table in fits file with the given name. + """ + final_images_catalog = [] + for lens_class, transform_matrices, expo_data in zip( + lens_class_list, transform_matrices_list, exposure_data_list + ): + variable_injected_image = variable_lens_injection( + lens_class, + band=band, + num_pix=num_pix, + transform_pix2angle=transform_matrices, + exposure_data=expo_data, + ) + if output_file is None: + final_images_catalog.append(variable_injected_image) + else: + first_table = not os.path.exists(output_file) + if first_table: + variable_injected_image.write(output_file, overwrite=True) + first_table = False + else: + fits_append_table(output_file, variable_injected_image) + if len(final_images_catalog) > 1: + return final_images_catalog + return None \ No newline at end of file From d4e0332f25666a231ed2e917ce844ff2edf78b78 Mon Sep 17 00:00:00 2001 From: narayan Date: Thu, 31 Oct 2024 12:38:32 -0400 Subject: [PATCH 62/87] modified ignore file in codcov --- codecov.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codecov.yml b/codecov.yml index 91e2fbb40..acc7a99c5 100644 --- a/codecov.yml +++ b/codecov.yml @@ -7,6 +7,6 @@ comment: # this is a top-level key require_base: false # [true :: must have a base report to post] require_head: true # [true :: must have a head report to post] ignore: - - "slsim/lsst_science_pipeline.py" + - "slsim/LsstSciencePipeline/lsst_science_pipeline.py" - "setup.py" - "tests/*" From b2fb5632975056a8592fddefccfa3fd9a5d912f0 Mon Sep 17 00:00:00 2001 From: narayan Date: Thu, 31 Oct 2024 12:45:04 -0400 Subject: [PATCH 63/87] transfered some of the function of opsim_pipeline module to util_lsst.py --- slsim/LsstSciencePipeline/opsim_pipeline.py | 70 ------------------ slsim/LsstSciencePipeline/util_lsst.py | 71 ++++++++++++++++++- .../test_opsim_pipeline.py | 6 +- 3 files changed, 71 insertions(+), 76 deletions(-) diff --git a/slsim/LsstSciencePipeline/opsim_pipeline.py b/slsim/LsstSciencePipeline/opsim_pipeline.py index e1d9f9867..f04c77495 100644 --- a/slsim/LsstSciencePipeline/opsim_pipeline.py +++ b/slsim/LsstSciencePipeline/opsim_pipeline.py @@ -1,7 +1,5 @@ import numpy as np from astropy.table import Table -from astropy.table import Column -from slsim.image_simulation import lens_image import lenstronomy.Util.util as util import lenstronomy.Util.kernel_util as kernel_util import lenstronomy.Util.data_util as data_util @@ -149,71 +147,3 @@ def opsim_time_series_images_data( table_data_list.append(table_data) return table_data_list - - -def opsim_variable_lens_injection( - lens_class, bands, num_pix, transform_pix2angle, exposure_data -): - """Injects variable lens to the OpSim time series data (1 object). - - :param lens_class: Lens() object - :param bands: list of imaging bands of interest - :param num_pix: number of pixels per axis - :param transform_pix2angle: transformation matrix (2x2) of pixels into coordinate - displacements - :param exposure_data: An astropy table of exposure data. One entry of - table_list_data generated from the opsim_time_series_images_data function. It - must contain the rms of background noise fluctuations (column name should be - "bkg_noise"), psf kernel for each exposure (column name should be "psf_kernel", - these are pixel psf kernel for each single exposure images in time series - image), observation time (column name should be "obs_time", these are - observation time in days for each single exposure images in time series images), - exposure time (column name should be "expo_time", these are exposure time for - each single exposure images in time series images), magnitude zero point (column - name should be "zero_point", these are zero point magnitudes for each single - exposure images in time series image), coordinates of the object (column name - should be "calexp_center"), these are the coordinates in (ra, dec), and the band - in which the observation is taken (column name should be "band"). - :return: Astropy table of injected lenses and exposure information of dp0 data - """ - - final_image = [] - - for obs in range(len(exposure_data["obs_time"])): - - exposure_data_obs = exposure_data[obs] - - if exposure_data_obs["band"] not in bands: - continue - - if "bkg_noise" in exposure_data_obs.keys(): - std_gaussian_noise = exposure_data_obs["bkg_noise"] - else: - std_gaussian_noise = None - - lens_images = lens_image( - lens_class, - band=exposure_data_obs["band"], - mag_zero_point=exposure_data_obs["zero_point"], - num_pix=num_pix, - psf_kernel=exposure_data_obs["psf_kernel"], - transform_pix2angle=transform_pix2angle, - exposure_time=exposure_data_obs["expo_time"], - t_obs=exposure_data_obs["obs_time"], - std_gaussian_noise=std_gaussian_noise, - ) - - final_image.append(lens_images) - - lens_col = Column(name="lens", data=final_image) - final_image_col = Column(name="injected_lens", data=final_image) - - # Create a new Table with only the bands of interest - expo_bands = np.array([b for b in exposure_data["band"]]) - mask = np.isin(expo_bands, bands) - exposure_data_new = exposure_data[mask] - - # if len(exposure_data_new) > 0: - exposure_data_new.add_columns([lens_col, final_image_col]) - - return exposure_data_new diff --git a/slsim/LsstSciencePipeline/util_lsst.py b/slsim/LsstSciencePipeline/util_lsst.py index c7581f9e6..5f29a0d48 100644 --- a/slsim/LsstSciencePipeline/util_lsst.py +++ b/slsim/LsstSciencePipeline/util_lsst.py @@ -1,6 +1,6 @@ import numpy as np from astropy.table import Column -from slsim.image_simulation import lens_image_series +from slsim.image_simulation import lens_image_series, lens_image from slsim.Util.param_util import fits_append_table import os @@ -119,4 +119,71 @@ def multiple_variable_lens_injection( fits_append_table(output_file, variable_injected_image) if len(final_images_catalog) > 1: return final_images_catalog - return None \ No newline at end of file + return None + +def opsim_variable_lens_injection( + lens_class, bands, num_pix, transform_pix2angle, exposure_data +): + """Injects variable lens to the OpSim time series data (1 object). + + :param lens_class: Lens() object + :param bands: list of imaging bands of interest + :param num_pix: number of pixels per axis + :param transform_pix2angle: transformation matrix (2x2) of pixels into coordinate + displacements + :param exposure_data: An astropy table of exposure data. One entry of + table_list_data generated from the opsim_time_series_images_data function. It + must contain the rms of background noise fluctuations (column name should be + "bkg_noise"), psf kernel for each exposure (column name should be "psf_kernel", + these are pixel psf kernel for each single exposure images in time series + image), observation time (column name should be "obs_time", these are + observation time in days for each single exposure images in time series images), + exposure time (column name should be "expo_time", these are exposure time for + each single exposure images in time series images), magnitude zero point (column + name should be "zero_point", these are zero point magnitudes for each single + exposure images in time series image), coordinates of the object (column name + should be "calexp_center"), these are the coordinates in (ra, dec), and the band + in which the observation is taken (column name should be "band"). + :return: Astropy table of injected lenses and exposure information of dp0 data + """ + + final_image = [] + + for obs in range(len(exposure_data["obs_time"])): + + exposure_data_obs = exposure_data[obs] + + if exposure_data_obs["band"] not in bands: + continue + + if "bkg_noise" in exposure_data_obs.keys(): + std_gaussian_noise = exposure_data_obs["bkg_noise"] + else: + std_gaussian_noise = None + + lens_images = lens_image( + lens_class, + band=exposure_data_obs["band"], + mag_zero_point=exposure_data_obs["zero_point"], + num_pix=num_pix, + psf_kernel=exposure_data_obs["psf_kernel"], + transform_pix2angle=transform_pix2angle, + exposure_time=exposure_data_obs["expo_time"], + t_obs=exposure_data_obs["obs_time"], + std_gaussian_noise=std_gaussian_noise, + ) + + final_image.append(lens_images) + + lens_col = Column(name="lens", data=final_image) + final_image_col = Column(name="injected_lens", data=final_image) + + # Create a new Table with only the bands of interest + expo_bands = np.array([b for b in exposure_data["band"]]) + mask = np.isin(expo_bands, bands) + exposure_data_new = exposure_data[mask] + + # if len(exposure_data_new) > 0: + exposure_data_new.add_columns([lens_col, final_image_col]) + + return exposure_data_new diff --git a/tests/test_LsstSciencePipeline/test_opsim_pipeline.py b/tests/test_LsstSciencePipeline/test_opsim_pipeline.py index d84829101..bae933998 100644 --- a/tests/test_LsstSciencePipeline/test_opsim_pipeline.py +++ b/tests/test_LsstSciencePipeline/test_opsim_pipeline.py @@ -3,10 +3,8 @@ from astropy.table import Table from astropy.cosmology import FlatLambdaCDM from slsim.lens import Lens -from slsim.LsstSciencePipeline.opsim_pipeline import ( - opsim_variable_lens_injection, - opsim_time_series_images_data, -) +from slsim.LsstSciencePipeline.opsim_pipeline import opsim_time_series_images_data +from slsim.LsstSciencePipeline.util_lsst import opsim_variable_lens_injection from slsim.Sources.source import Source from slsim.Deflectors.deflector import Deflector import pytest From e44bd7335047debb0dbf1a64078cea01a1275e8c Mon Sep 17 00:00:00 2001 From: narayan Date: Thu, 31 Oct 2024 12:48:16 -0400 Subject: [PATCH 64/87] adjusted a opsim notebook. --- .../Opsim_supernovae_plus_extended_source_tutorial.ipynb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/notebooks/Opsim_supernovae_plus_extended_source_tutorial.ipynb b/notebooks/Opsim_supernovae_plus_extended_source_tutorial.ipynb index da1288464..fad58cd40 100644 --- a/notebooks/Opsim_supernovae_plus_extended_source_tutorial.ipynb +++ b/notebooks/Opsim_supernovae_plus_extended_source_tutorial.ipynb @@ -26,7 +26,8 @@ "from slsim.image_simulation import lens_image_series, sharp_rgb_image\n", "from slsim.Plots.plot_functions import create_image_montage_from_image_list\n", "from slsim.image_simulation import point_source_coordinate_properties\n", - "from slsim.LsstSciencePipeline import opsim_pipeline" + "from slsim.LsstSciencePipeline import opsim_pipeline\n", + "from slsim.LsstSciencePipeline.util_lsst import opsim_variable_lens_injection" ] }, { @@ -477,7 +478,7 @@ "num_pix = 200\n", "transform_pix2angle = np.array([[0.2, 0], [0, 0.2]])\n", "\n", - "images = opsim_pipeline.opsim_variable_lens_injection(\n", + "images = opsim_variable_lens_injection(\n", " lens_class, bands, num_pix, transform_pix2angle, exposure_data[index]\n", ")\n", "print(\"images.keys() : \", images.keys())\n", From 4ec81b3ee64f1271dd49e05612d36e72b6357ae2 Mon Sep 17 00:00:00 2001 From: narayan Date: Thu, 31 Oct 2024 12:50:00 -0400 Subject: [PATCH 65/87] adjusted a variable lens injection notebook. --- ...ction_of_variable_lenses_in_dp0_time_series_images.ipynb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/notebooks/injection_of_variable_lenses_in_dp0_time_series_images.ipynb b/notebooks/injection_of_variable_lenses_in_dp0_time_series_images.ipynb index b7aa2a7ab..d2d71494a 100644 --- a/notebooks/injection_of_variable_lenses_in_dp0_time_series_images.ipynb +++ b/notebooks/injection_of_variable_lenses_in_dp0_time_series_images.ipynb @@ -18,10 +18,8 @@ "source": [ "import numpy as np\n", "from slsim.Util.param_util import random_radec_string\n", - "from slsim.lsst_science_pipeline import (\n", - " multiple_variable_lens_injection,\n", - " dp0_time_series_images_data,\n", - ")\n", + "from slsim.lsst_science_pipeline import dp0_time_series_images_data\n", + "from slsim.LsstSciencePipeline.util_lsst import multiple_variable_lens_injection\n", "import lsst.daf.butler as dafButler\n", "from astropy.cosmology import FlatLambdaCDM\n", "from astropy.units import Quantity\n", From dd463a97a5ac6c20d5105e80a7f7aac791ce061b Mon Sep 17 00:00:00 2001 From: narayan Date: Thu, 31 Oct 2024 12:50:58 -0400 Subject: [PATCH 66/87] excluded opsim_pipeline from codecov. --- codecov.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/codecov.yml b/codecov.yml index acc7a99c5..b46238621 100644 --- a/codecov.yml +++ b/codecov.yml @@ -8,5 +8,6 @@ comment: # this is a top-level key require_head: true # [true :: must have a head report to post] ignore: - "slsim/LsstSciencePipeline/lsst_science_pipeline.py" + - "slsim/LsstSciencePipeline/opsim_pipeline.py" - "setup.py" - "tests/*" From 910cf5fe03e01b38843d05c513131769230d0e88 Mon Sep 17 00:00:00 2001 From: narayan Date: Fri, 1 Nov 2024 13:01:41 -0400 Subject: [PATCH 67/87] minor change. --- slsim/lens.py | 74 +++++++++++++++++++++++++-------------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/slsim/lens.py b/slsim/lens.py index 67b60d66c..fafa3387c 100644 --- a/slsim/lens.py +++ b/slsim/lens.py @@ -78,19 +78,28 @@ def __init__( if isinstance(source_class, list): self.source = source_class - self.single_source_class = max(self.source, key=lambda obj: obj.redshift) + # choose a highest resdshift source to use conventionally use in lens + # mass model. + self.max_redshift_source_class = max( + self.source, key=lambda obj: obj.redshift) self.source_number = len(self.source) - self._single_source_index = self.source.index(self.single_source_class) + self._max_redshift_source_index = self.source.index( + self.max_redshift_source_class) else: self.source = [source_class] self.source_number = 1 - self.single_source_class = source_class - self._single_source_index = 0 - self._source_type = self.single_source_class.source_type + # this is for single source case. self.max_redshift_source_class and + # self.source are the same class. The difference is only that one is in the + # form of list and other is just a Source instance. This is done just for + # the completion of routine to make things consistent in both single source + # and double source case. + self.max_redshift_source_class = source_class + self._max_redshift_source_index = 0 + self._source_type = self.max_redshift_source_class.source_type # we conventionally use highest source redshift in the lens cosmo. self._lens_cosmo = LensCosmo( z_lens=float(self.deflector.redshift), - z_source=float(self.single_source_class.redshift), + z_source=float(self.max_redshift_source_class.redshift), cosmo=self.cosmo, ) @@ -290,11 +299,8 @@ def external_convergence(self): :return: external convergence """ - kappa_list = [] - for los_linear_dist in self.los_linear_distortions: - _, _, kappa_ext = los_linear_dist - kappa_list.append(kappa_ext) - return kappa_list + _, _, kappa_ext = self.los_linear_distortions + return kappa_ext @property def external_shear(self): @@ -302,12 +308,9 @@ def external_shear(self): :return: the absolute external shear """ - shear_list = [] - for los_linear_dist in self.los_linear_distortions: - gamma1, gamma2, _ = los_linear_dist - shear_list.append((gamma1**2 + gamma2**2) ** 0.5) - return shear_list - + gamma1, gamma2, _ = self.los_linear_distortions + return (gamma1**2 + gamma2**2) ** 0.5 + @property def einstein_radius_deflector(self): """Einstein radius, from SIS approximation (coming from velocity dispersion) @@ -344,14 +347,16 @@ def einstein_radius_deflector(self): @property def einstein_radius(self): """Einstein radius, from SIS approximation (coming from velocity dispersion) + - external convergence effect for each lens-source pair. + external convergence effect. - :return: Einstein radius [arc seconds] + :return: list of Einstein radius [arc seconds] for each lens source pair. """ - theta_E_list = self.einstein_radius_deflector - los_distortions = self.los_linear_distortions - return [theta_E / (1 - kappa_ext) for theta_E, (_, _, kappa_ext) in zip( - theta_E_list, los_distortions)] + theta_E = self.einstein_radius_deflector + _, _, kappa_ext = self.los_linear_distortions + theta_E_list = [] + for i in range(len(theta_E)): + theta_E_list.append(theta_E[i]/(1 - kappa_ext)) + return theta_E_list def deflector_ellipticity(self): """ @@ -385,18 +390,14 @@ def los_linear_distortions(self): return self._los_linear_distortions_cache def _calculate_los_linear_distortions(self): - """Line-of-sight distortions in shear and convergence for each source. + """Line-of-sight distortions in shear and convergence. :return: kappa, gamma1, gamma2 """ - los_distortions = [] - for source in self.source: - gamma1, gamma2, kappa = self.los_config.calculate_los_linear_distortions( - source_redshift=source.redshift, - deflector_redshift=self.deflector_redshift - ) - los_distortions.append((gamma1, gamma2, kappa)) - return los_distortions + return self.los_config.calculate_los_linear_distortions( + source_redshift=self.max_redshift_source_class.redshift, + deflector_redshift=self.deflector_redshift, + ) def deflector_magnitude(self, band): """Apparent magnitude of the deflector for a given band. @@ -621,9 +622,9 @@ def lenstronomy_kwargs(self, band=None): kwargs_model["lens_redshift_list"] = [ self.deflector_redshift]*len(lens_mass_model_list) kwargs_model["z_lens"] = self.deflector_redshift - kwargs_model["z_source"] = self.single_source_class.redshift + kwargs_model["z_source"] = self.max_redshift_source_class.redshift kwargs_model["source_redshift_list"] = self.source_redshift - kwargs_model["z_source_convention"] = self.single_source_class.redshift + kwargs_model["z_source_convention"]= self.max_redshift_source_class.redshift kwargs_model["cosmo"] = self.cosmo sources, sources_kwargs = self.source_light_model_lenstronomy(band=band) @@ -658,8 +659,7 @@ def deflector_mass_model_lenstronomy(self): % self.deflector.deflector_type ) # adding line-of-sight structure - gamma1, gamma2, kappa_ext = self.los_linear_distortions[ - self._single_source_index] + gamma1, gamma2, kappa_ext = self.los_linear_distortions gamma1_lenstronomy, gamma2_lenstronomy = ellipticity_slsim_to_lenstronomy( e1_slsim=gamma1, e2_slsim=gamma2 ) @@ -706,7 +706,7 @@ def source_light_model_lenstronomy(self, band=None): draw_area=self.test_area, center_lens=self.deflector_position, band=band )) #lets transform list in to required structure - if (self.single_source_class.light_profile == "double_sersic" and + if (self.max_redshift_source_class.light_profile == "double_sersic" and self.source_number > 1): source_models_list_restructure = source_models_list kwargs_source_list_restructure = kwargs_source_list From f8f526f021d6633736ba5bd578645326b6fc1011 Mon Sep 17 00:00:00 2001 From: narayan Date: Fri, 1 Nov 2024 16:10:32 -0400 Subject: [PATCH 68/87] minor change. --- slsim/lens.py | 21 +++++++++++++++------ slsim/lensed_system_base.py | 4 ++-- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/slsim/lens.py b/slsim/lens.py index fafa3387c..6d007c8e1 100644 --- a/slsim/lens.py +++ b/slsim/lens.py @@ -14,6 +14,7 @@ from lenstronomy.Util import util from slsim.lensed_system_base import LensedSystemBase +import warnings class Lens(LensedSystemBase): @@ -283,15 +284,15 @@ def deflector_redshift(self): return self.deflector.redshift @property - def source_redshift(self): + def source_redshift_list(self): """ :return: list of source redshifts """ - source_redshift_list = [] + source_redshifts = [] for source in self.source: - source_redshift_list.append(source.redshift) - return source_redshift_list + source_redshifts.append(source.redshift) + return source_redshifts @property def external_convergence(self): @@ -316,7 +317,7 @@ def einstein_radius_deflector(self): """Einstein radius, from SIS approximation (coming from velocity dispersion) without line-of-sight correction. - :return: + :return: list of einstein radius of each lens-source pair. """ if not hasattr(self, "_theta_E_list"): self._theta_E_list = [] @@ -445,6 +446,13 @@ def image_observer_times(self, t_obs): :rtype: numpy array. Each element of the array corresponds to different image observation times. """ + #TODO: This is not implemented for point source in lenstronomy. Need to update + # when lenstronomy new version is public. + warning_msg = ( + "Multi source lensing is not implemented for point source." + " So, this function need to be updated in future." + ) + warnings.warn(warning_msg, category=UserWarning, stacklevel=2) observer_times_list = [] for point_source_arrival_time in self.point_source_arrival_times(): arrival_times = point_source_arrival_time @@ -554,6 +562,7 @@ def extended_source_magnification(self): :return: list of integrated magnification factor of host magnitude """ + #TODO: add source redshift in ray_shooting. Wait for lenstronomy new version. if not hasattr(self, "_extended_source_magnification"): kwargs_model, kwargs_params = self.lenstronomy_kwargs(band=None) theta_E_list = self.einstein_radius @@ -623,7 +632,7 @@ def lenstronomy_kwargs(self, band=None): self.deflector_redshift]*len(lens_mass_model_list) kwargs_model["z_lens"] = self.deflector_redshift kwargs_model["z_source"] = self.max_redshift_source_class.redshift - kwargs_model["source_redshift_list"] = self.source_redshift + kwargs_model["source_redshift_list"] = self.source_redshift_list kwargs_model["z_source_convention"]= self.max_redshift_source_class.redshift kwargs_model["cosmo"] = self.cosmo diff --git a/slsim/lensed_system_base.py b/slsim/lensed_system_base.py index f09172b98..970303623 100644 --- a/slsim/lensed_system_base.py +++ b/slsim/lensed_system_base.py @@ -52,10 +52,10 @@ def deflector_redshift(self): pass @abstractmethod - def source_redshift(self): + def source_redshift_list(self): """Source redshift. - :return: source redshift + :return: list of each source redshift """ pass From 9db89d2cb70d238d1f5f725241651948eeb003c4 Mon Sep 17 00:00:00 2001 From: narayan Date: Mon, 4 Nov 2024 16:11:39 -0500 Subject: [PATCH 69/87] minor change. --- slsim/lens.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slsim/lens.py b/slsim/lens.py index 6d007c8e1..9bc8398c8 100644 --- a/slsim/lens.py +++ b/slsim/lens.py @@ -715,7 +715,7 @@ def source_light_model_lenstronomy(self, band=None): draw_area=self.test_area, center_lens=self.deflector_position, band=band )) #lets transform list in to required structure - if (self.max_redshift_source_class.light_profile == "double_sersic" and + if (self.single_source_class.light_profile == "double_sersic" and self.source_number > 1): source_models_list_restructure = source_models_list kwargs_source_list_restructure = kwargs_source_list From a6e4b8e82fe2d9e110187d17231f895dbb00a5a5 Mon Sep 17 00:00:00 2001 From: narayan Date: Mon, 4 Nov 2024 16:48:41 -0500 Subject: [PATCH 70/87] minor change. --- slsim/LsstSciencePipeline/util_lsst.py | 4 ++-- slsim/lens.py | 2 +- slsim/lens_pop.py | 3 ++- tests/test_lens.py | 16 ++++++++-------- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/slsim/LsstSciencePipeline/util_lsst.py b/slsim/LsstSciencePipeline/util_lsst.py index 5f29a0d48..f688edbf2 100644 --- a/slsim/LsstSciencePipeline/util_lsst.py +++ b/slsim/LsstSciencePipeline/util_lsst.py @@ -32,8 +32,8 @@ def variable_lens_injection( # lightcurve time. So, we use random observation time from the lens class lightcurve # time to ensure simulation of reasonable images. observation_time = np.random.uniform( - min(lens_class.single_source_class.lightcurve_time), - max(lens_class.single_source_class.lightcurve_time), + min(lens_class.max_redshift_source_class.lightcurve_time), + max(lens_class.max_redshift_source_class.lightcurve_time), size=len(exposure_data["obs_time"]), ) observation_time.sort() diff --git a/slsim/lens.py b/slsim/lens.py index 9bc8398c8..6d007c8e1 100644 --- a/slsim/lens.py +++ b/slsim/lens.py @@ -715,7 +715,7 @@ def source_light_model_lenstronomy(self, band=None): draw_area=self.test_area, center_lens=self.deflector_position, band=band )) #lets transform list in to required structure - if (self.single_source_class.light_profile == "double_sersic" and + if (self.max_redshift_source_class.light_profile == "double_sersic" and self.source_number > 1): source_models_list_restructure = source_models_list kwargs_source_list_restructure = kwargs_source_list diff --git a/slsim/lens_pop.py b/slsim/lens_pop.py index b1fd2dedf..26e56dc47 100644 --- a/slsim/lens_pop.py +++ b/slsim/lens_pop.py @@ -84,6 +84,7 @@ def select_lens_at_random(self, test_area=None, **kwargs_lens_cut): light """ while True: + #This creates a single deflector - single_source lens. source = self._sources.draw_source() lens = self._lens_galaxies.draw_deflector() _lens = Deflector( @@ -222,7 +223,7 @@ def draw_population(self, kwargs_lens_cuts, speed_factor=1): if lens_class.validity_test(**kwargs_lens_cuts): valid_sources.append(_source) n += 1 - if valid_sources: + if len(valid_sources) > 0: # Use a single source if only one source is valid, else use # the list of valid sources if len(valid_sources) == 1: diff --git a/tests/test_lens.py b/tests/test_lens.py index 34b60bcc3..e63dd6df3 100644 --- a/tests/test_lens.py +++ b/tests/test_lens.py @@ -379,9 +379,9 @@ def test_different_setting(self): cosmo=self.cosmo, los_config=los1, ) - assert gg_lens.external_shear[0] >= 0 - assert isinstance(gg_lens.external_convergence[0], float) - assert isinstance(gg_lens.external_shear[0], float) + assert gg_lens.external_shear >= 0 + assert isinstance(gg_lens.external_convergence, float) + assert isinstance(gg_lens.external_shear, float) los2 = LOSConfig( los_bool=True, @@ -395,9 +395,9 @@ def test_different_setting(self): cosmo=self.cosmo, los_config=los2, ) - assert gg_lens_2.external_shear[0] >= 0 - assert isinstance(gg_lens_2.external_convergence, list) - assert isinstance(gg_lens_2.external_shear, list) + assert gg_lens_2.external_shear >= 0 + assert isinstance(gg_lens_2.external_convergence, float) + assert isinstance(gg_lens_2.external_shear, float) los3 = LOSConfig(los_bool=False) gg_lens_3 = Lens( @@ -406,8 +406,8 @@ def test_different_setting(self): cosmo=self.cosmo, los_config=los3, ) - assert gg_lens_3.external_convergence[0] == 0 - assert gg_lens_3.external_shear[0] == 0 + assert gg_lens_3.external_convergence == 0 + assert gg_lens_3.external_shear == 0 los4 = LOSConfig( los_bool=True, From 26a17d1a42b839dd5f4d3f253b1e19c1e4d6821a Mon Sep 17 00:00:00 2001 From: narayan Date: Tue, 5 Nov 2024 11:08:20 -0500 Subject: [PATCH 71/87] minor change. --- slsim/lens.py | 101 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 98 insertions(+), 3 deletions(-) diff --git a/slsim/lens.py b/slsim/lens.py index 6d007c8e1..760180fb2 100644 --- a/slsim/lens.py +++ b/slsim/lens.py @@ -193,12 +193,28 @@ def point_source_image_positions(self): magnification_limit=self._magnification_limit, )) return self._point_image_positions - + def validity_test( self, min_image_separation=0, max_image_separation=10, mag_arc_limit=None, + ): + """Check if the lensing configuration is valid for each source.""" + for source in self.sources: + if not self._validity_test(source, min_image_separation, + max_image_separation, mag_arc_limit): + return False + return True + + + def _validity_test( + self, + source, + einstein_radius, + min_image_separation=0, + max_image_separation=10, + mag_arc_limit=None, ): """Check whether lensing configuration matches selection and plausibility criteria. @@ -210,6 +226,85 @@ def validity_test( :type mag_arc_limit: dict with key of bands and values of magnitude limits :return: boolean """ + # Criteria 1:The redshift of the lens (z_lens) must be less than the + # redshift of the source (z_source). + z_lens = self.deflector.redshift + z_source = source.redshift + if z_lens >= z_source: + return False + + # Criteria 2: The angular Einstein radius of the lensing configuration (theta_E) + # times 2 must be greater than or equal to the minimum image separation + # (min_image_separation) and less than or equal to the maximum image + # separation (max_image_separation). + if not min_image_separation <= 2 * einstein_radius <= max_image_separation: + return False + + # Criteria 3: The distance between the lens center and the source position + # must be less than or equal to the angular Einstein radius + # of the lensing configuration (times sqrt(2)). + center_lens, center_source = ( + self.deflector_position, + source.point_source_position( + center_lens=self.deflector_position, draw_area=self.test_area + ), + ) + if np.sum((center_lens - center_source) ** 2) > einstein_radius**2 * 2: + return False + + # Criteria 4: The lensing configuration must produce at least two SL images. + image_positions = self.point_source_image_positions() + if len(image_positions[0]) < 2: + return False + + # Criteria 5: The maximum separation between any two image positions must be + # greater than or equal to the minimum image separation and less than or + # equal to the maximum image separation. + image_separation = image_separation_from_positions(image_positions) + if not min_image_separation <= image_separation <= max_image_separation: + return False + + # Criteria 6: (optional) + # compute the magnified brightness of the lensed extended arc for different + # bands at least in one band, the magnitude has to be brighter than the limit + if mag_arc_limit is not None and self._source_type in [ + "extended", + "point_plus_extended", + ]: + # makes sure magnification of extended source is only used when there is + # an extended source + bool_mag_limit = False + host_mag = self.extended_source_magnification() + for band, mag_limit_band in mag_arc_limit.items(): + mag_source = self.extended_source_magnitude(band) + mag_arc = mag_source - 2.5 * np.log10( + host_mag + ) # lensing magnification results in a shift in magnitude + if mag_arc < mag_limit_band: + bool_mag_limit = True + break + if bool_mag_limit is False: + return False + # TODO make similar criteria for point source magnitudes + return True + # TODO: test for signal-to-noise ratio in surface brightness + + """def validity_test( + self, + min_image_separation=0, + max_image_separation=10, + mag_arc_limit=None, + ): + Check whether lensing configuration matches selection and plausibility + criteria. + + :param min_image_separation: minimum image separation + :param max_image_separation: maximum image separation + :param mag_arc_limit: dictionary with key of bands and values of magnitude + limits of integrated lensed arc + :type mag_arc_limit: dict with key of bands and values of magnitude limits + :return: boolean + for index, (source, einstein_radius) in enumerate(zip( self.source, self.einstein_radius)): # Criteria 1:The redshift of the lens (z_lens) must be less than the @@ -270,9 +365,9 @@ def validity_test( bool_mag_limit = True break if bool_mag_limit is False: - return False + return False""" # TODO make similar criteria for point source magnitudes - return True + #return True # TODO: test for signal-to-noise ratio in surface brightness @property From cec3d96e8ab43dcaa1bdc912e1093759f371236b Mon Sep 17 00:00:00 2001 From: narayan Date: Tue, 5 Nov 2024 11:29:58 -0500 Subject: [PATCH 72/87] minor change. --- slsim/lens.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/slsim/lens.py b/slsim/lens.py index 760180fb2..d358442f8 100644 --- a/slsim/lens.py +++ b/slsim/lens.py @@ -194,29 +194,30 @@ def point_source_image_positions(self): )) return self._point_image_positions - def validity_test( + """def validity_test( self, min_image_separation=0, max_image_separation=10, mag_arc_limit=None, ): - """Check if the lensing configuration is valid for each source.""" + #Check if the lensing configuration is valid for each source. for source in self.sources: if not self._validity_test(source, min_image_separation, max_image_separation, mag_arc_limit): return False - return True + return True""" - def _validity_test( + """def _validity_test( self, source, einstein_radius, + image_positions, min_image_separation=0, max_image_separation=10, mag_arc_limit=None, ): - """Check whether lensing configuration matches selection and plausibility + Check whether lensing configuration matches selection and plausibility criteria. :param min_image_separation: minimum image separation @@ -225,7 +226,7 @@ def _validity_test( limits of integrated lensed arc :type mag_arc_limit: dict with key of bands and values of magnitude limits :return: boolean - """ + # Criteria 1:The redshift of the lens (z_lens) must be less than the # redshift of the source (z_source). z_lens = self.deflector.redshift @@ -253,7 +254,7 @@ def _validity_test( return False # Criteria 4: The lensing configuration must produce at least two SL images. - image_positions = self.point_source_image_positions() + image_positions = image_positions if len(image_positions[0]) < 2: return False @@ -267,7 +268,7 @@ def _validity_test( # Criteria 6: (optional) # compute the magnified brightness of the lensed extended arc for different # bands at least in one band, the magnitude has to be brighter than the limit - if mag_arc_limit is not None and self._source_type in [ + if mag_arc_limit is not None and source.source_type in [ "extended", "point_plus_extended", ]: @@ -284,18 +285,18 @@ def _validity_test( bool_mag_limit = True break if bool_mag_limit is False: - return False + return False""" # TODO make similar criteria for point source magnitudes - return True + #return True # TODO: test for signal-to-noise ratio in surface brightness - """def validity_test( + def validity_test( self, min_image_separation=0, max_image_separation=10, mag_arc_limit=None, ): - Check whether lensing configuration matches selection and plausibility + """Check whether lensing configuration matches selection and plausibility criteria. :param min_image_separation: minimum image separation @@ -303,7 +304,7 @@ def _validity_test( :param mag_arc_limit: dictionary with key of bands and values of magnitude limits of integrated lensed arc :type mag_arc_limit: dict with key of bands and values of magnitude limits - :return: boolean + :return: boolean""" for index, (source, einstein_radius) in enumerate(zip( self.source, self.einstein_radius)): @@ -365,7 +366,7 @@ def _validity_test( bool_mag_limit = True break if bool_mag_limit is False: - return False""" + return False # TODO make similar criteria for point source magnitudes #return True # TODO: test for signal-to-noise ratio in surface brightness From 5604eaefc970039aad32082cb46a913f401f9573 Mon Sep 17 00:00:00 2001 From: narayan Date: Tue, 5 Nov 2024 11:31:54 -0500 Subject: [PATCH 73/87] minor change. --- slsim/lens.py | 102 ++------------------------------------------------ 1 file changed, 3 insertions(+), 99 deletions(-) diff --git a/slsim/lens.py b/slsim/lens.py index d358442f8..6d007c8e1 100644 --- a/slsim/lens.py +++ b/slsim/lens.py @@ -193,102 +193,6 @@ def point_source_image_positions(self): magnification_limit=self._magnification_limit, )) return self._point_image_positions - - """def validity_test( - self, - min_image_separation=0, - max_image_separation=10, - mag_arc_limit=None, - ): - #Check if the lensing configuration is valid for each source. - for source in self.sources: - if not self._validity_test(source, min_image_separation, - max_image_separation, mag_arc_limit): - return False - return True""" - - - """def _validity_test( - self, - source, - einstein_radius, - image_positions, - min_image_separation=0, - max_image_separation=10, - mag_arc_limit=None, - ): - Check whether lensing configuration matches selection and plausibility - criteria. - - :param min_image_separation: minimum image separation - :param max_image_separation: maximum image separation - :param mag_arc_limit: dictionary with key of bands and values of magnitude - limits of integrated lensed arc - :type mag_arc_limit: dict with key of bands and values of magnitude limits - :return: boolean - - # Criteria 1:The redshift of the lens (z_lens) must be less than the - # redshift of the source (z_source). - z_lens = self.deflector.redshift - z_source = source.redshift - if z_lens >= z_source: - return False - - # Criteria 2: The angular Einstein radius of the lensing configuration (theta_E) - # times 2 must be greater than or equal to the minimum image separation - # (min_image_separation) and less than or equal to the maximum image - # separation (max_image_separation). - if not min_image_separation <= 2 * einstein_radius <= max_image_separation: - return False - - # Criteria 3: The distance between the lens center and the source position - # must be less than or equal to the angular Einstein radius - # of the lensing configuration (times sqrt(2)). - center_lens, center_source = ( - self.deflector_position, - source.point_source_position( - center_lens=self.deflector_position, draw_area=self.test_area - ), - ) - if np.sum((center_lens - center_source) ** 2) > einstein_radius**2 * 2: - return False - - # Criteria 4: The lensing configuration must produce at least two SL images. - image_positions = image_positions - if len(image_positions[0]) < 2: - return False - - # Criteria 5: The maximum separation between any two image positions must be - # greater than or equal to the minimum image separation and less than or - # equal to the maximum image separation. - image_separation = image_separation_from_positions(image_positions) - if not min_image_separation <= image_separation <= max_image_separation: - return False - - # Criteria 6: (optional) - # compute the magnified brightness of the lensed extended arc for different - # bands at least in one band, the magnitude has to be brighter than the limit - if mag_arc_limit is not None and source.source_type in [ - "extended", - "point_plus_extended", - ]: - # makes sure magnification of extended source is only used when there is - # an extended source - bool_mag_limit = False - host_mag = self.extended_source_magnification() - for band, mag_limit_band in mag_arc_limit.items(): - mag_source = self.extended_source_magnitude(band) - mag_arc = mag_source - 2.5 * np.log10( - host_mag - ) # lensing magnification results in a shift in magnitude - if mag_arc < mag_limit_band: - bool_mag_limit = True - break - if bool_mag_limit is False: - return False""" - # TODO make similar criteria for point source magnitudes - #return True - # TODO: test for signal-to-noise ratio in surface brightness def validity_test( self, @@ -304,8 +208,8 @@ def validity_test( :param mag_arc_limit: dictionary with key of bands and values of magnitude limits of integrated lensed arc :type mag_arc_limit: dict with key of bands and values of magnitude limits - :return: boolean""" - + :return: boolean + """ for index, (source, einstein_radius) in enumerate(zip( self.source, self.einstein_radius)): # Criteria 1:The redshift of the lens (z_lens) must be less than the @@ -368,7 +272,7 @@ def validity_test( if bool_mag_limit is False: return False # TODO make similar criteria for point source magnitudes - #return True + return True # TODO: test for signal-to-noise ratio in surface brightness @property From 4f6eb6a6b9d8ebbfdd8ae92cfcc1794e423cdc42 Mon Sep 17 00:00:00 2001 From: narayan Date: Tue, 5 Nov 2024 15:56:17 -0500 Subject: [PATCH 74/87] refactored functions into single and multiple source. --- slsim/lens.py | 492 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 301 insertions(+), 191 deletions(-) diff --git a/slsim/lens.py b/slsim/lens.py index 6d007c8e1..f301d1bbb 100644 --- a/slsim/lens.py +++ b/slsim/lens.py @@ -128,7 +128,19 @@ def deflector_position(self): return self.deflector.deflector_center def extended_source_image_positions(self): - """Returns extended source image positions by solving the lens equation. + """Returns extended source image positions by solving the lens equation for + each source. + + :return: list of (x-pos, y-pos) + """ + image_position_list = [] + for source in self.source: + image_position_list.append(self._extended_source_image_positions(source)) + return image_position_list + + def _extended_source_image_positions(self, source): + """Returns extended source image positions by solving the lens equation for a + single source. :return: x-pos, y-pos """ @@ -136,33 +148,44 @@ def extended_source_image_positions(self): lens_model_list, kwargs_lens = self.deflector_mass_model_lenstronomy() lens_model_class = LensModel(lens_model_list=lens_model_list) lens_eq_solver = LensEquationSolver(lens_model_class) - self._image_positions = [] - for source, einstein_radius in zip(self.source, self.einstein_radius): - source_pos_x, source_pos_y = source.extended_source_position( - center_lens=self.deflector_position, draw_area=self.test_area - ) - if ( - self._lens_equation_solver == "lenstronomy_analytical" - and analytical_lens_model_support(lens_model_list) is True - ): - solver = "analytical" - else: - solver = "lenstronomy" - self._image_positions.append(lens_eq_solver.image_position_from_source( - source_pos_x, - source_pos_y, - kwargs_lens, - solver=solver, - search_window=einstein_radius * 6, - min_distance=einstein_radius * 6 / 200, - magnification_limit=self._magnification_limit, - )) + source_pos_x, source_pos_y = source.extended_source_position( + center_lens=self.deflector_position, draw_area=self.test_area + ) + if ( + self._lens_equation_solver == "lenstronomy_analytical" + and analytical_lens_model_support(lens_model_list) is True + ): + solver = "analytical" + else: + solver = "lenstronomy" + einstein_radius = self._einstein_radius(source) + self._image_positions = lens_eq_solver.image_position_from_source( + source_pos_x, + source_pos_y, + kwargs_lens, + solver=solver, + search_window=einstein_radius * 6, + min_distance=einstein_radius * 6 / 200, + magnification_limit=self._magnification_limit, + ) return self._image_positions def point_source_image_positions(self): - """Returns point source image positions by solving the lens equation. In the - absence of a point source, this function returns the solution for the center of - the extended source. + """Returns point source image positions by solving the lens equation for all + sources. In the absence of a point source, this function returns the solution + for the center of the extended source. + + :return: list of (x-pos, y-pos) for each source + """ + ps_image_position_list = [] + for source in self.source: + ps_image_position_list.append(self._point_source_image_positions(source)) + return ps_image_position_list + + def _point_source_image_positions(self, source): + """Returns point source image positions by solving the lens equation for a + single source. In the absence of a point source, this function returns the + solution for the center of the extended source. :return: x-pos, y-pos """ @@ -170,28 +193,27 @@ def point_source_image_positions(self): lens_model_list, kwargs_lens = self.deflector_mass_model_lenstronomy() lens_model_class = LensModel(lens_model_list=lens_model_list) lens_eq_solver = LensEquationSolver(lens_model_class) - self._point_image_positions = [] - for source, einstein_radius in zip(self.source, self.einstein_radius): - point_source_pos_x, point_source_pos_y = source.point_source_position( - center_lens=self.deflector_position, draw_area=self.test_area - ) - # uses analytical lens equation solver in case it is supported by lenstronomy for speed-up - if ( - self._lens_equation_solver == "lenstronomy_analytical" - and analytical_lens_model_support(lens_model_list) is True - ): - solver = "analytical" - else: - solver = "lenstronomy" - self._point_image_positions.append(lens_eq_solver.image_position_from_source( - point_source_pos_x, - point_source_pos_y, - kwargs_lens, - solver=solver, - search_window=einstein_radius * 6, - min_distance=einstein_radius * 6 / 200, - magnification_limit=self._magnification_limit, - )) + point_source_pos_x, point_source_pos_y = source.point_source_position( + center_lens=self.deflector_position, draw_area=self.test_area + ) + # uses analytical lens equation solver in case it is supported by lenstronomy for speed-up + if ( + self._lens_equation_solver == "lenstronomy_analytical" + and analytical_lens_model_support(lens_model_list) is True + ): + solver = "analytical" + else: + solver = "lenstronomy" + einstein_radius = self._einstein_radius(source) + self._point_image_positions = lens_eq_solver.image_position_from_source( + point_source_pos_x, + point_source_pos_y, + kwargs_lens, + solver=solver, + search_window=einstein_radius * 6, + min_distance=einstein_radius * 6 / 200, + magnification_limit=self._magnification_limit, + ) return self._point_image_positions def validity_test( @@ -319,32 +341,39 @@ def einstein_radius_deflector(self): :return: list of einstein radius of each lens-source pair. """ - if not hasattr(self, "_theta_E_list"): - self._theta_E_list = [] - for source in self.source: - if self.deflector.redshift >= source.redshift: - self._theta_E_list.append(0) - elif self.deflector.deflector_type in ["EPL"]: - _lens_cosmo = LensCosmo( - z_lens=float(self.deflector.redshift), - z_source=float(source.redshift), - cosmo=self.cosmo, - ) - _theta_E = _lens_cosmo.sis_sigma_v2theta_E( - float(self.deflector.velocity_dispersion(cosmo=self.cosmo)) - ) - self._theta_E_list.append(_theta_E) - else: - # numerical solution for the Einstein radius - lens_model_list, kwargs_lens = self.deflector_mass_model_lenstronomy() - lens_model = LensModel(lens_model_list=lens_model_list) - lens_analysis = LensProfileAnalysis(lens_model=lens_model) - _theta_E = lens_analysis.effective_einstein_radius( - kwargs_lens, r_min=1e-3, r_max=5e1, num_points=50 - ) - self._theta_E_list.append(_theta_E) + self._theta_E_list = [] + for source in self.source: + self._theta_E_list.append(self._einstein_radius_deflector(source)) return self._theta_E_list + def _einstein_radius_deflector(self, source): + """Einstein radius, from SIS approximation (coming from velocity dispersion) + without line-of-sight correction. + + :return: einstein radius of a lens-source pair. + """ + if not hasattr(self, "_theta_E"): + if self.deflector.redshift >= source.redshift: + self._theta_E = 0 + elif self.deflector.deflector_type in ["EPL"]: + _lens_cosmo = LensCosmo( + z_lens=float(self.deflector.redshift), + z_source=float(source.redshift), + cosmo=self.cosmo, + ) + self._theta_E = _lens_cosmo.sis_sigma_v2theta_E( + float(self.deflector.velocity_dispersion(cosmo=self.cosmo)) + ) + else: + # numerical solution for the Einstein radius + lens_model_list, kwargs_lens = self.deflector_mass_model_lenstronomy() + lens_model = LensModel(lens_model_list=lens_model_list) + lens_analysis = LensProfileAnalysis(lens_model=lens_model) + self._theta_E = lens_analysis.effective_einstein_radius( + kwargs_lens, r_min=1e-3, r_max=5e1, num_points=50 + ) + return self._theta_E + @property def einstein_radius(self): """Einstein radius, from SIS approximation (coming from velocity dispersion) + @@ -352,12 +381,21 @@ def einstein_radius(self): :return: list of Einstein radius [arc seconds] for each lens source pair. """ - theta_E = self.einstein_radius_deflector - _, _, kappa_ext = self.los_linear_distortions theta_E_list = [] - for i in range(len(theta_E)): - theta_E_list.append(theta_E[i]/(1 - kappa_ext)) + for source in self.source: + theta_E_list.append(self._einstein_radius(source=source)) return theta_E_list + + def _einstein_radius(self, source): + """Einstein radius, from SIS approximation (coming from velocity dispersion) + + external convergence effect. + + :param source: Source class instance. + :return: Einstein radius [arc seconds] + """ + theta_E = self._einstein_radius_deflector(source) + _, _, kappa_ext = self.los_linear_distortions + return theta_E / (1 - kappa_ext) def deflector_ellipticity(self): """ @@ -417,22 +455,31 @@ def point_source_arrival_times(self): :return: list of arrival times for each image [days] for each source. :rtype: list of numpy array """ - lens_model_list, kwargs_lens = self.deflector_mass_model_lenstronomy() - image_pos = self.point_source_image_positions() arrival_times_list = [] - for index, source in enumerate(self.source): - lens_model = LensModel( - lens_model_list=lens_model_list, - cosmo=self.cosmo, - z_lens=self.deflector_redshift, - z_source=source.redshift, - ) - x_image, y_image = image_pos[index] - arrival_times = lens_model.arrival_time( - x_image, y_image, kwargs_lens=kwargs_lens - ) - arrival_times_list.append(arrival_times) + for source in self.source: + arrival_times_list.append(self._point_source_arrival_times(source)) return arrival_times_list + + def _point_source_arrival_times(self, source): + """Arrival time of images relative to a straight line without lensing. Negative + values correspond to images arriving earlier, and positive signs correspond to + images arriving later. + + :return: arrival times for each image [days] + :rtype: numpy array + """ + lens_model_list, kwargs_lens = self.deflector_mass_model_lenstronomy() + lens_model = LensModel( + lens_model_list=lens_model_list, + cosmo=self.cosmo, + z_lens=self.deflector_redshift, + z_source=source.redshift, + ) + x_image, y_image = self._point_source_image_positions(source) + arrival_times = lens_model.arrival_time( + x_image, y_image, kwargs_lens=kwargs_lens + ) + return arrival_times def image_observer_times(self, t_obs): """Calculates time of the source at the different images, not correcting for @@ -443,7 +490,7 @@ def image_observer_times(self, t_obs): or an array of observation time. :return: time of the source when seen in the different images (without redshift correction) - :rtype: numpy array. Each element of the array corresponds to different image + :rtype: list of numpy array. Each element of the array corresponds to different image observation times. """ #TODO: This is not implemented for point source in lenstronomy. Need to update @@ -454,24 +501,39 @@ def image_observer_times(self, t_obs): ) warnings.warn(warning_msg, category=UserWarning, stacklevel=2) observer_times_list = [] - for point_source_arrival_time in self.point_source_arrival_times(): - arrival_times = point_source_arrival_time - if type(t_obs) is np.ndarray and len(t_obs) > 1: - observer_times = ( - t_obs[:, np.newaxis] - arrival_times + np.min(arrival_times) - ).T - else: - observer_times = (t_obs - arrival_times + np.min(arrival_times))[ - :, np.newaxis - ] - observer_times_list.append(observer_times) + for source in self.source: + observer_times_list.append(self._image_observer_times(source, t_obs)) if self.source_number == 1: return observer_times_list[0] return observer_times_list + + def _image_observer_times(self, source, t_obs): + """Calculates time of a source at the different images, not correcting for + redshifts, but for time delays. The time is relative to the first arriving + image. + + :param t_obs: time of observation [days]. It could be a single observation time + or an array of observation time. + :return: time of the source when seen in the different images (without redshift + correction) + :rtype: numpy array. Each element of the array corresponds to different image + observation times. + """ + arrival_times = self._point_source_arrival_times(source) + if type(t_obs) is np.ndarray and len(t_obs) > 1: + observer_times = ( + t_obs[:, np.newaxis] - arrival_times + np.min(arrival_times) + ).T + else: + observer_times = (t_obs - arrival_times + np.min(arrival_times))[ + :, np.newaxis + ] + + return observer_times def point_source_magnitude(self, band, lensed=False, time=None): """Point source magnitudes, either unlensed (single value) or lensed (array) with - macro-model magnifications. + macro-model magnifications. This function provided magnitudes of all the sources. # TODO: time-variability with micro-lensing @@ -481,43 +543,56 @@ def point_source_magnitude(self, band, lensed=False, time=None): :type lensed: bool :param time: time is a image observation time in units of days. If None, provides magnitude without variability. - :return: point source magnitude or a list of point source magnitudes. + :return: list of point source magnitudes. """ + magnitude_list = [] + for source in self.source: + magnitude_list.append(self._point_source_magnitude( + band, source, lensed=lensed, time=time)) + return magnitude_list + + def _point_source_magnitude(self, band, source, lensed=False, time=None): + """Point source magnitude, either unlensed (single value) or lensed (array) with + macro-model magnifications. This function does operation only for the single source. + + # TODO: time-variability with micro-lensing + + :param band: imaging band + :type band: string + :param lensed: if True, returns the lensed magnified magnitude + :type lensed: bool + :param time: time is a image observation time in units of days. If None, + provides magnitude without variability. + :return: point source magnitude of a single source + """ + # TODO: might have to change conventions between extended and point source if lensed: - magnif_list = self.point_source_magnification() - abs_magnif_list = [abs(i) for i in magnif_list] - magnif_log_list = 2.5 * np.log10(abs_magnif_list) - #loop through all the source - magnitude_list = [] - for index, (source, magnif_log) in enumerate(zip( - self.source, magnif_log_list)): - if time is not None: - time = time - image_observed_times = self.image_observer_times(time)[index] - variable_magnitude = source.point_source_magnitude( - band, - image_observation_times=image_observed_times, - ) - lensed_variable_magnitude = ( - variable_magnitude - magnif_log[:, np.newaxis] - ) - magnitude_list.append(lensed_variable_magnitude) - else: - source_mag_unlensed = source.point_source_magnitude(band) - magnified_mag_list = [] - for i in range(len(magnif_log)): - magnified_mag_list.append(source_mag_unlensed - magnif_log[i]) - magnitude_list.append(np.array(magnified_mag_list)) - else: - magnitude_list = [] - for source in self.source: - magnitude_list.append(source.point_source_magnitude(band)) - return magnitude_list + magnif = self._point_source_magnification(source) + magnif_log = 2.5 * np.log10(abs(magnif)) + if time is not None: + time = time + image_observed_times = self._image_observer_times(source, time) + variable_magnitude = source.point_source_magnitude( + band, + image_observation_times=image_observed_times, + ) + lensed_variable_magnitude = ( + variable_magnitude - magnif_log[:, np.newaxis] + ) + return lensed_variable_magnitude + else: + source_mag_unlensed = source.point_source_magnitude(band) + magnified_mag_list = [] + for i in range(len(magnif_log)): + magnified_mag_list.append(source_mag_unlensed - magnif_log[i]) + return np.array(magnified_mag_list) + return source.point_source_magnitude(band) def extended_source_magnitude(self, band, lensed=False): """Unlensed apparent magnitude of the extended source for a given band (assumes - that size is the same for different bands) + that size is the same for different bands). This function gives gives magnitude + for all the provided sources. :param band: imaging band :type band: string @@ -527,88 +602,123 @@ def extended_source_magnitude(self, band, lensed=False): """ # band_string = str("mag_" + band) # TODO: might have to change conventions between extended and point source - magnification_list = self.extended_source_magnification() magnitude_list = [] #loop through each source. for index, source in enumerate(self.source): - source_mag = source.extended_source_magnitude(band) - if lensed: - mag = magnification_list[index] - lensed_mag = source_mag - 2.5 * np.log10(mag) - magnitude_list.append(lensed_mag) - else: - magnitude_list.append(source_mag) + magnitude_list.append(self._extended_source_magnitude(band, source, + index, lensed=lensed)) return magnitude_list + + def _extended_source_magnitude(self, band, source, source_index, lensed=False): + """Unlensed apparent magnitude of the extended source for a given band (assumes + that size is the same for different bands). This function gives magnitude of a + single source. + + :param band: imaging band + :type band: string + :param source: Source class instance + :param source_index: index of a source in source list. + :param lensed: if True, returns the lensed magnified magnitude + :type lensed: bool + :return: magnitude of source in given band + """ + # band_string = str("mag_" + band) + # TODO: might have to change conventions between extended and point source + source_mag = source.extended_source_magnitude(band) + if lensed: + mag = self._extended_single_source_magnification(source, source_index) + return source_mag - 2.5 * np.log10(mag) + return source_mag def point_source_magnification(self): - """Macro-model magnification of point sources. + """Macro-model magnification of point sources. This function calculates + magnification for each sources. :return: list of signed magnification of point sources in same order as image positions. """ + self._ps_magnification_list = [] + for source in self.source: + self._ps_magnification_list.append( + self._point_source_magnification(source)) + return self._ps_magnification_list + + def _point_source_magnification(self, source): + """Macro-model magnification of a point source. This is for a single source. + + :return: signed magnification of a point source in same order as image positions + """ if not hasattr(self, "_ps_magnification"): lens_model_list, kwargs_lens = self.deflector_mass_model_lenstronomy() lensModel = LensModel(lens_model_list=lens_model_list) - self._ps_magnification_list = [] - for image_pos in self.point_source_image_positions(): - img_x, img_y = image_pos - self._ps_magnification_list.append(lensModel.magnification(img_x, img_y, - kwargs_lens)) - return self._ps_magnification_list + img_x, img_y = self._point_source_image_positions(source) + self._ps_magnification = lensModel.magnification(img_x, img_y, kwargs_lens) + return self._ps_magnification def extended_source_magnification(self): """Compute the extended lensed surface brightness and calculates the integrated flux-weighted magnification factor of each extended host galaxy . - :return: list of integrated magnification factor of host magnitude + :return: list of integrated magnification factor of host magnitude for each + source """ #TODO: add source redshift in ray_shooting. Wait for lenstronomy new version. + + self._extended_source_magnification_list = [] + for index, source in enumerate(self.source): + self._extended_source_magnification_list.append( + self._extended_single_source_magnification(source, index)) + return self._extended_source_magnification_list + + def _extended_single_source_magnification(self, source, source_index): + """Compute the extended lensed surface brightness and calculates the integrated + flux-weighted magnification factor of the extended host galaxy. This function + does the operation for single source. + + :param source: Source class instance + :param source_index: index of a source in source list. + :return: integrated magnification factor of host magnitude + """ if not hasattr(self, "_extended_source_magnification"): kwargs_model, kwargs_params = self.lenstronomy_kwargs(band=None) - theta_E_list = self.einstein_radius - self._extended_source_magnification_list = [] - # loop through each source. - for index, source in enumerate(self.source): - _light_model_list = kwargs_model.get( - "source_light_model_list", [])[index] - kwargs_source_mag = [kwargs_params["kwargs_source"][index]] - if isinstance(_light_model_list, list): - light_model_list = _light_model_list - else: - light_model_list = [_light_model_list] - lightModel = LightModel( - light_model_list=light_model_list) - lensModel = LensModel( - lens_model_list=kwargs_model.get("lens_model_list", []) - ) - theta_E = theta_E_list[index] - center_source = source.extended_source_position( - center_lens=self.deflector_position, draw_area=self.test_area - ) + _light_model_list = kwargs_model.get( + "source_light_model_list", [])[source_index] + kwargs_source_mag = [kwargs_params["kwargs_source"][source_index]] + if isinstance(_light_model_list, list): + light_model_list = _light_model_list + else: + light_model_list = [_light_model_list] + lightModel = LightModel( + light_model_list=light_model_list) + lensModel = LensModel( + lens_model_list=kwargs_model.get("lens_model_list", []) + ) + theta_E = self._einstein_radius(source) + center_source = source.extended_source_position( + center_lens=self.deflector_position, draw_area=self.test_area + ) - kwargs_source_amp = data_util.magnitude2amplitude( - lightModel, kwargs_source_mag, magnitude_zero_point=0 - ) + kwargs_source_amp = data_util.magnitude2amplitude( + lightModel, kwargs_source_mag, magnitude_zero_point=0 + ) - num_pix = 200 - delta_pix = theta_E * 4 / num_pix - x, y = util.make_grid(numPix=num_pix, deltapix=delta_pix) - x += center_source[0] - y += center_source[1] - beta_x, beta_y = lensModel.ray_shooting(x, y, kwargs_params["kwargs_lens"]) - flux_lensed = np.sum( - lightModel.surface_brightness(beta_x, beta_y, kwargs_source_amp) - ) - flux_no_lens = np.sum( - lightModel.surface_brightness(x, y, kwargs_source_amp) - ) - if flux_no_lens > 0: - self._extended_source_magnification = flux_lensed / flux_no_lens - else: - self._extended_source_magnification = 0 - self._extended_source_magnification_list.append( - self._extended_source_magnification) - return self._extended_source_magnification_list + num_pix = 200 + delta_pix = theta_E * 4 / num_pix + x, y = util.make_grid(numPix=num_pix, deltapix=delta_pix) + x += center_source[0] + y += center_source[1] + beta_x, beta_y = lensModel.ray_shooting(x, y, kwargs_params["kwargs_lens"]) + flux_lensed = np.sum( + lightModel.surface_brightness(beta_x, beta_y, kwargs_source_amp) + ) + flux_no_lens = np.sum( + lightModel.surface_brightness(x, y, kwargs_source_amp) + ) + if flux_no_lens > 0: + self._extended_source_magnification = flux_lensed / flux_no_lens + else: + self._extended_source_magnification = 0 + return self._extended_source_magnification def lenstronomy_kwargs(self, band=None): """Generates lenstronomy dictionary conventions for the class object. From a8b3bb7292977a36044ba16b18c8266a3307bcfc Mon Sep 17 00:00:00 2001 From: narayan Date: Tue, 5 Nov 2024 16:17:32 -0500 Subject: [PATCH 75/87] added multi source option in Lens class and updated lenstronomy version requirement. --- requirements.txt | 2 +- slsim/lens.py | 32 +++++++++++++++++++++++++++----- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/requirements.txt b/requirements.txt index c7a2c680a..9802fc7d8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ skypy -lenstronomy>=1.11.10 +lenstronomy>=1.12.1 astropy>=5.2 numpy scipy<=1.13.1 diff --git a/slsim/lens.py b/slsim/lens.py index f301d1bbb..612d7eb95 100644 --- a/slsim/lens.py +++ b/slsim/lens.py @@ -146,7 +146,11 @@ def _extended_source_image_positions(self, source): """ if not hasattr(self, "_image_positions"): lens_model_list, kwargs_lens = self.deflector_mass_model_lenstronomy() - lens_model_class = LensModel(lens_model_list=lens_model_list) + lens_model_class = LensModel(lens_model_list=lens_model_list, + z_lens=self.deflector_redshift, + z_source_convention=self.max_redshift_source_class.redshift, + multi_plane=False, + z_source=source.redshift) lens_eq_solver = LensEquationSolver(lens_model_class) source_pos_x, source_pos_y = source.extended_source_position( center_lens=self.deflector_position, draw_area=self.test_area @@ -191,7 +195,11 @@ def _point_source_image_positions(self, source): """ if not hasattr(self, "_point_image_positions"): lens_model_list, kwargs_lens = self.deflector_mass_model_lenstronomy() - lens_model_class = LensModel(lens_model_list=lens_model_list) + lens_model_class = LensModel(lens_model_list=lens_model_list, + z_lens=self.deflector_redshift, + z_source_convention=self.max_redshift_source_class.redshift, + multi_plane=False, + z_source=source.redshift) lens_eq_solver = LensEquationSolver(lens_model_class) point_source_pos_x, point_source_pos_y = source.point_source_position( center_lens=self.deflector_position, draw_area=self.test_area @@ -367,7 +375,11 @@ def _einstein_radius_deflector(self, source): else: # numerical solution for the Einstein radius lens_model_list, kwargs_lens = self.deflector_mass_model_lenstronomy() - lens_model = LensModel(lens_model_list=lens_model_list) + lens_model = LensModel(lens_model_list=lens_model_list, + z_lens=self.deflector_redshift, + z_source_convention=self.max_redshift_source_class.redshift, + multi_plane=False, + z_source=source.redshift) lens_analysis = LensProfileAnalysis(lens_model=lens_model) self._theta_E = lens_analysis.effective_einstein_radius( kwargs_lens, r_min=1e-3, r_max=5e1, num_points=50 @@ -474,6 +486,8 @@ def _point_source_arrival_times(self, source): cosmo=self.cosmo, z_lens=self.deflector_redshift, z_source=source.redshift, + z_source_convention=self.max_redshift_source_class.redshift, + multi_plane=False ) x_image, y_image = self._point_source_image_positions(source) arrival_times = lens_model.arrival_time( @@ -650,7 +664,11 @@ def _point_source_magnification(self, source): """ if not hasattr(self, "_ps_magnification"): lens_model_list, kwargs_lens = self.deflector_mass_model_lenstronomy() - lensModel = LensModel(lens_model_list=lens_model_list) + lensModel = LensModel(lens_model_list=lens_model_list, + z_lens=self.deflector_redshift, + z_source_convention=self.max_redshift_source_class.redshift, + multi_plane=False, + z_source=source.redshift) img_x, img_y = self._point_source_image_positions(source) self._ps_magnification = lensModel.magnification(img_x, img_y, kwargs_lens) return self._ps_magnification @@ -691,7 +709,11 @@ def _extended_single_source_magnification(self, source, source_index): lightModel = LightModel( light_model_list=light_model_list) lensModel = LensModel( - lens_model_list=kwargs_model.get("lens_model_list", []) + lens_model_list=kwargs_model.get("lens_model_list", []), + z_lens=self.deflector_redshift, + z_source_convention=self.max_redshift_source_class.redshift, + multi_plane=False, + z_source=source.redshift ) theta_E = self._einstein_radius(source) center_source = source.extended_source_position( From a1c15ae806f24cecf14304450a210cc10f37ac54 Mon Sep 17 00:00:00 2001 From: narayan Date: Tue, 5 Nov 2024 16:18:24 -0500 Subject: [PATCH 76/87] minor change --- slsim/lens.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/slsim/lens.py b/slsim/lens.py index 612d7eb95..dd594b8ca 100644 --- a/slsim/lens.py +++ b/slsim/lens.py @@ -14,8 +14,6 @@ from lenstronomy.Util import util from slsim.lensed_system_base import LensedSystemBase -import warnings - class Lens(LensedSystemBase): """Class to manage individual lenses.""" @@ -507,13 +505,6 @@ def image_observer_times(self, t_obs): :rtype: list of numpy array. Each element of the array corresponds to different image observation times. """ - #TODO: This is not implemented for point source in lenstronomy. Need to update - # when lenstronomy new version is public. - warning_msg = ( - "Multi source lensing is not implemented for point source." - " So, this function need to be updated in future." - ) - warnings.warn(warning_msg, category=UserWarning, stacklevel=2) observer_times_list = [] for source in self.source: observer_times_list.append(self._image_observer_times(source, t_obs)) From 1793de7b7935242e25cc0f7d465e2499752f88d0 Mon Sep 17 00:00:00 2001 From: narayan Date: Tue, 5 Nov 2024 17:40:26 -0500 Subject: [PATCH 77/87] minor change --- slsim/lens.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/slsim/lens.py b/slsim/lens.py index dd594b8ca..d017db14c 100644 --- a/slsim/lens.py +++ b/slsim/lens.py @@ -857,19 +857,16 @@ def source_light_model_lenstronomy(self, band=None): self._source_type == "point_source" or self._source_type == "point_plus_extended" ): - image_pos_list = self.point_source_image_positions() - image_magnif_list = np.abs(self.point_source_magnification()) - magnitude_list = self.point_source_magnitude( - band=band, lensed=True) source_models_list = [] kwargs_ps_list = [] - for index, source in enumerate(self.source): + for source in self.source: source_models_list.append("LENSED_POSITION") - img_x, img_y = image_pos_list[index] + img_x, img_y = self._point_source_image_positions(source=source) if band is None: - image_magnitudes = image_magnif_list[index] + image_magnitudes = np.abs(self._point_source_magnification(source)) else: - image_magnitudes = magnitude_list[index] + image_magnitudes = self._point_source_magnitude( + band=band, source=source, lensed=True) kwargs_ps_list.append( {"ra_image": img_x, "dec_image": img_y, "magnitude": image_magnitudes}) From 69638ff7d6f7fbf75656eb9700edd29ae9556cf2 Mon Sep 17 00:00:00 2001 From: narayan Date: Tue, 5 Nov 2024 19:47:44 -0500 Subject: [PATCH 78/87] added test functions for multisource Lens class. --- tests/test_lens.py | 120 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/tests/test_lens.py b/tests/test_lens.py index e63dd6df3..3627b880d 100644 --- a/tests/test_lens.py +++ b/tests/test_lens.py @@ -508,7 +508,127 @@ def test_double_sersic_multisource(supernovae_lens_instance_double_sersic_multis assert len(results[1]["kwargs_source"]) == 2 assert len(results[1]["kwargs_ps"]) == 2 +class TestMultiSource(object): + def setup_method(self): + self.cosmo = FlatLambdaCDM(H0=70, Om0=0.3) + path = os.path.dirname(__file__) + source_dict1 = Table.read( + os.path.join(path, "TestData/source_supernovae_new.fits"), format="fits" + ) + data={'ra_off':[-0.2524832112858584], + 'dec_off':[0.1394853307977928], + 'sep':[0.288450913482674], + 'z':[0.65], + 'R_d':[3.515342568459843], + 'R_s':[2.0681117132023721], + 'logMstar':[10.7699], + 'logSFR':[0.6924], + 'a0': [0.51747227], + 'a1': [0.32622826], + 'a_rot':[0.2952329149503528], + 'b0':[0.25262737], + 'b1':[0.27223456], + 'e':[0.3303168046302505], + 'ellipticity0': [0.33939099024025735], + 'ellipticity1': [0.0802206575082465], + 'mag_g': [22.936048], + 'mag_i':[21.78715], + 'mag_r':[22.503948], + 'n_sersic_0':[1.0], + 'n_sersic_1':[4.0], + 'w0':[0.907], + 'w1':[0.093], + 'e0_1':[0.14733325180101145], + 'e0_2':[0.09874724195027847], + 'e1_1':[0.03754887782202202], + 'e1_2':[0.025166403903583694], + 'angular_size0':[0.37156280037917327], + 'angular_size1':[0.29701108506340096]} + source_dict2 = Table(data) + deflector_dict = Table.read( + os.path.join(path, "TestData/deflector_supernovae_new.fits"), format="fits" + ) + self.source1 = Source( + source_dict=source_dict1, + cosmo=self.cosmo, + source_type="point_plus_extended", + light_profile="double_sersic", + lightcurve_time=np.linspace(-20, 100, 1000), + variability_model="light_curve", + kwargs_variability={"supernovae_lightcurve", "i"}, + sn_type="Ia", + sn_absolute_mag_band="bessellb", + sn_absolute_zpsys="ab", + + ) + self.source2 = Source( + source_dict=source_dict2, + cosmo=self.cosmo, + source_type="point_plus_extended", + light_profile="double_sersic", + lightcurve_time=np.linspace(-20, 100, 1000), + variability_model="light_curve", + kwargs_variability={"supernovae_lightcurve", "i"}, + sn_type="Ia", + sn_absolute_mag_band="bessellb", + sn_absolute_zpsys="ab", + + ) + self.deflector = Deflector( + deflector_type="EPL", + deflector_dict=deflector_dict, + ) + lens_class1 = Lens( + deflector_class=self.deflector, + source_class=self.source1, + cosmo=self.cosmo, + ) + lens_class2 = Lens( + deflector_class=self.deflector, + source_class=self.source2, + cosmo=self.cosmo, + ) + lens_class3 = Lens( + deflector_class=self.deflector, + source_class=[self.source1, self.source2], + cosmo=self.cosmo, + ) + point_source_arival_time1=lens_class1.point_source_arrival_times() + point_source_arival_time2=lens_class2.point_source_arrival_times() + point_source_arival_time3=lens_class3.point_source_arrival_times() + + ps_magnification1=lens_class1.point_source_magnification() + ps_magnification2=lens_class2.point_source_magnification() + ps_magnification3=lens_class3.point_source_magnification() + + es_magnification1=lens_class1.extended_source_magnification() + es_magnification2=lens_class2.extended_source_magnification() + es_magnification3=lens_class3.extended_source_magnification() + + einstein_radius1=lens_class1.einstein_radius + einstein_radius2=lens_class2.einstein_radius + einstein_radius3=lens_class3.einstein_radius + observation_time = 50 + image_observation_time1=lens_class1.image_observer_times(observation_time) + image_observation_time2=lens_class2.image_observer_times(observation_time) + image_observation_time3=lens_class3.image_observer_times(observation_time) + #Test multisource point source arival time. + assert point_source_arival_time1[0] == point_source_arival_time3[0] + assert point_source_arival_time2[0] == point_source_arival_time3[1] + #Test multisource point source magnifications. + assert ps_magnification1[0] == ps_magnification3[0] + assert ps_magnification2[0] == ps_magnification3[1] + #Test multisource extended source magnifications. + assert es_magnification1[0] == es_magnification3[0] + assert es_magnification2[0] == es_magnification3[1] + #Test multisource einstein radius. + assert einstein_radius1[0] == einstein_radius3[0] + assert einstein_radius2[0] == einstein_radius3[1] + #Test multisource image observation time + assert image_observation_time1[0] == image_observation_time3[0] + assert image_observation_time2[0] == image_observation_time3[1] + if __name__ == "__main__": pytest.main() From a16e0fa993e9d03e5614514dfda19f91bb26e726 Mon Sep 17 00:00:00 2001 From: narayan Date: Tue, 5 Nov 2024 20:17:00 -0500 Subject: [PATCH 79/87] devided validity test function for single and multiple source. --- slsim/lens.py | 155 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 93 insertions(+), 62 deletions(-) diff --git a/slsim/lens.py b/slsim/lens.py index d017db14c..6a7b3d5b0 100644 --- a/slsim/lens.py +++ b/slsim/lens.py @@ -221,14 +221,42 @@ def _point_source_image_positions(self, source): magnification_limit=self._magnification_limit, ) return self._point_image_positions + + def validity_test(self, + min_image_separation=0, + max_image_separation=10, + mag_arc_limit=None): + """Check whether multiple lensing configuration matches selection and plausibility + criteria. + + :param min_image_separation: minimum image separation + :param max_image_separation: maximum image separation + :param mag_arc_limit: dictionary with key of bands and values of magnitude + limits of integrated lensed arc + :type mag_arc_limit: dict with key of bands and values of magnitude limits + :return: boolean + """ + validity_boolean = [] + for index, source in enumerate(self.source): + validity_boolean.append(self._validity_test(source, + min_image_separation=min_image_separation, + max_image_separation=max_image_separation, + mag_arc_limit=mag_arc_limit, + source_index=index)) + if np.all(validity_boolean) == True: + return True + else: + return False - def validity_test( + def _validity_test( self, + source, min_image_separation=0, max_image_separation=10, mag_arc_limit=None, + source_index=None ): - """Check whether lensing configuration matches selection and plausibility + """Check whether a single lensing configuration matches selection and plausibility criteria. :param min_image_separation: minimum image separation @@ -238,67 +266,70 @@ def validity_test( :type mag_arc_limit: dict with key of bands and values of magnitude limits :return: boolean """ - for index, (source, einstein_radius) in enumerate(zip( - self.source, self.einstein_radius)): - # Criteria 1:The redshift of the lens (z_lens) must be less than the - # redshift of the source (z_source). - z_lens = self.deflector.redshift - z_source = source.redshift - if z_lens >= z_source: - return False - - # Criteria 2: The angular Einstein radius of the lensing configuration (theta_E) - # times 2 must be greater than or equal to the minimum image separation - # (min_image_separation) and less than or equal to the maximum image - # separation (max_image_separation). - if not min_image_separation <= 2 * einstein_radius <= max_image_separation: - return False - - # Criteria 3: The distance between the lens center and the source position - # must be less than or equal to the angular Einstein radius - # of the lensing configuration (times sqrt(2)). - center_lens, center_source = ( - self.deflector_position, - source.point_source_position( - center_lens=self.deflector_position, draw_area=self.test_area - ), - ) - if np.sum((center_lens - center_source) ** 2) > einstein_radius**2 * 2: - return False - - # Criteria 4: The lensing configuration must produce at least two SL images. - image_positions = self.point_source_image_positions()[index] - if len(image_positions[0]) < 2: - return False - - # Criteria 5: The maximum separation between any two image positions must be - # greater than or equal to the minimum image separation and less than or - # equal to the maximum image separation. - image_separation = image_separation_from_positions(image_positions) - if not min_image_separation <= image_separation <= max_image_separation: + + # Criteria 1:The redshift of the lens (z_lens) must be less than the + # redshift of the source (z_source). + z_lens = self.deflector.redshift + z_source = source.redshift + if z_lens >= z_source: + return False + + # Criteria 2: The angular Einstein radius of the lensing configuration (theta_E) + # times 2 must be greater than or equal to the minimum image separation + # (min_image_separation) and less than or equal to the maximum image + # separation (max_image_separation). + if not min_image_separation <= 2 * self._einstein_radius(source)\ + <= max_image_separation: + return False + + # Criteria 3: The distance between the lens center and the source position + # must be less than or equal to the angular Einstein radius + # of the lensing configuration (times sqrt(2)). + center_lens, center_source = ( + self.deflector_position, + source.point_source_position( + center_lens=self.deflector_position, draw_area=self.test_area + ), + ) + if np.sum((center_lens - center_source) ** 2) > \ + self._einstein_radius(source)**2 * 2: + return False + + # Criteria 4: The lensing configuration must produce at least two SL images. + image_positions = self._point_source_image_positions(source) + if len(image_positions[0]) < 2: + return False + + # Criteria 5: The maximum separation between any two image positions must be + # greater than or equal to the minimum image separation and less than or + # equal to the maximum image separation. + image_separation = image_separation_from_positions(image_positions) + if not min_image_separation <= image_separation <= max_image_separation: + return False + + # Criteria 6: (optional) + # compute the magnified brightness of the lensed extended arc for different + # bands at least in one band, the magnitude has to be brighter than the limit + if mag_arc_limit is not None and source.source_type in [ + "extended", + "point_plus_extended", + ]: + # makes sure magnification of extended source is only used when there is + # an extended source + bool_mag_limit = False + host_mag = self._extended_single_source_magnification( + source, source_index) + for band, mag_limit_band in mag_arc_limit.items(): + mag_source = self._extended_source_magnitude(band, source, + source_index) + mag_arc = mag_source - 2.5 * np.log10( + host_mag + ) # lensing magnification results in a shift in magnitude + if mag_arc < mag_limit_band: + bool_mag_limit = True + break + if bool_mag_limit is False: return False - - # Criteria 6: (optional) - # compute the magnified brightness of the lensed extended arc for different - # bands at least in one band, the magnitude has to be brighter than the limit - if mag_arc_limit is not None and self._source_type in [ - "extended", - "point_plus_extended", - ]: - # makes sure magnification of extended source is only used when there is - # an extended source - bool_mag_limit = False - host_mag = self.extended_source_magnification()[index] - for band, mag_limit_band in mag_arc_limit.items(): - mag_source = self.extended_source_magnitude(band)[index] - mag_arc = mag_source - 2.5 * np.log10( - host_mag - ) # lensing magnification results in a shift in magnitude - if mag_arc < mag_limit_band: - bool_mag_limit = True - break - if bool_mag_limit is False: - return False # TODO make similar criteria for point source magnitudes return True # TODO: test for signal-to-noise ratio in surface brightness From 3440f30376de87a31e2a2f63bf083157639313cb Mon Sep 17 00:00:00 2001 From: narayan Date: Wed, 6 Nov 2024 10:44:42 -0500 Subject: [PATCH 80/87] worked on Simon's comments. --- slsim/lens.py | 320 ++++++++++++++++++++++++++------------------------ 1 file changed, 166 insertions(+), 154 deletions(-) diff --git a/slsim/lens.py b/slsim/lens.py index 6a7b3d5b0..12cb1d0d9 100644 --- a/slsim/lens.py +++ b/slsim/lens.py @@ -131,45 +131,49 @@ def extended_source_image_positions(self): :return: list of (x-pos, y-pos) """ - image_position_list = [] - for source in self.source: - image_position_list.append(self._extended_source_image_positions(source)) - return image_position_list + if not hasattr(self, "_es_image_positions"): + self._es_image_position_list = [] + for source in self.source: + self._es_image_position_list.append( + self._extended_source_image_positions(source)) + return self._es_image_position_list def _extended_source_image_positions(self, source): """Returns extended source image positions by solving the lens equation for a single source. + :param source: Source class instance. The redshift of this source is used in + the LensModel. :return: x-pos, y-pos """ - if not hasattr(self, "_image_positions"): - lens_model_list, kwargs_lens = self.deflector_mass_model_lenstronomy() - lens_model_class = LensModel(lens_model_list=lens_model_list, - z_lens=self.deflector_redshift, - z_source_convention=self.max_redshift_source_class.redshift, - multi_plane=False, - z_source=source.redshift) - lens_eq_solver = LensEquationSolver(lens_model_class) - source_pos_x, source_pos_y = source.extended_source_position( - center_lens=self.deflector_position, draw_area=self.test_area - ) - if ( - self._lens_equation_solver == "lenstronomy_analytical" - and analytical_lens_model_support(lens_model_list) is True - ): - solver = "analytical" - else: - solver = "lenstronomy" - einstein_radius = self._einstein_radius(source) - self._image_positions = lens_eq_solver.image_position_from_source( - source_pos_x, - source_pos_y, - kwargs_lens, - solver=solver, - search_window=einstein_radius * 6, - min_distance=einstein_radius * 6 / 200, - magnification_limit=self._magnification_limit, - ) + + lens_model_list, kwargs_lens = self.deflector_mass_model_lenstronomy() + lens_model_class = LensModel(lens_model_list=lens_model_list, + z_lens=self.deflector_redshift, + z_source_convention=self.max_redshift_source_class.redshift, + multi_plane=False, + z_source=source.redshift) + lens_eq_solver = LensEquationSolver(lens_model_class) + source_pos_x, source_pos_y = source.extended_source_position( + center_lens=self.deflector_position, draw_area=self.test_area + ) + if ( + self._lens_equation_solver == "lenstronomy_analytical" + and analytical_lens_model_support(lens_model_list) is True + ): + solver = "analytical" + else: + solver = "lenstronomy" + einstein_radius = self._einstein_radius(source) + self._image_positions = lens_eq_solver.image_position_from_source( + source_pos_x, + source_pos_y, + kwargs_lens, + solver=solver, + search_window=einstein_radius * 6, + min_distance=einstein_radius * 6 / 200, + magnification_limit=self._magnification_limit, + ) return self._image_positions def point_source_image_positions(self): @@ -179,47 +183,50 @@ def point_source_image_positions(self): :return: list of (x-pos, y-pos) for each source """ - ps_image_position_list = [] - for source in self.source: - ps_image_position_list.append(self._point_source_image_positions(source)) - return ps_image_position_list + if not hasattr(self, "_ps_image_position_list"): + self._ps_image_position_list = [] + for source in self.source: + self._ps_image_position_list.append( + self._point_source_image_positions(source)) + return self._ps_image_position_list def _point_source_image_positions(self, source): """Returns point source image positions by solving the lens equation for a single source. In the absence of a point source, this function returns the solution for the center of the extended source. + :param source: Source class instance. The redshift of this source is used in + the LensModel. :return: x-pos, y-pos """ - if not hasattr(self, "_point_image_positions"): - lens_model_list, kwargs_lens = self.deflector_mass_model_lenstronomy() - lens_model_class = LensModel(lens_model_list=lens_model_list, - z_lens=self.deflector_redshift, - z_source_convention=self.max_redshift_source_class.redshift, - multi_plane=False, - z_source=source.redshift) - lens_eq_solver = LensEquationSolver(lens_model_class) - point_source_pos_x, point_source_pos_y = source.point_source_position( - center_lens=self.deflector_position, draw_area=self.test_area - ) - # uses analytical lens equation solver in case it is supported by lenstronomy for speed-up - if ( - self._lens_equation_solver == "lenstronomy_analytical" - and analytical_lens_model_support(lens_model_list) is True - ): - solver = "analytical" - else: - solver = "lenstronomy" - einstein_radius = self._einstein_radius(source) - self._point_image_positions = lens_eq_solver.image_position_from_source( - point_source_pos_x, - point_source_pos_y, - kwargs_lens, - solver=solver, - search_window=einstein_radius * 6, - min_distance=einstein_radius * 6 / 200, - magnification_limit=self._magnification_limit, - ) + lens_model_list, kwargs_lens = self.deflector_mass_model_lenstronomy() + lens_model_class = LensModel(lens_model_list=lens_model_list, + z_lens=self.deflector_redshift, + z_source_convention=self.max_redshift_source_class.redshift, + multi_plane=False, + z_source=source.redshift) + lens_eq_solver = LensEquationSolver(lens_model_class) + point_source_pos_x, point_source_pos_y = source.point_source_position( + center_lens=self.deflector_position, draw_area=self.test_area + ) + # uses analytical lens equation solver in case it is supported by lenstronomy for speed-up + if ( + self._lens_equation_solver == "lenstronomy_analytical" + and analytical_lens_model_support(lens_model_list) is True + ): + solver = "analytical" + else: + solver = "lenstronomy" + einstein_radius = self._einstein_radius(source) + self._point_image_positions = lens_eq_solver.image_position_from_source( + point_source_pos_x, + point_source_pos_y, + kwargs_lens, + solver=solver, + search_window=einstein_radius * 6, + min_distance=einstein_radius * 6 / 200, + magnification_limit=self._magnification_limit, + ) return self._point_image_positions def validity_test(self, @@ -378,41 +385,43 @@ def einstein_radius_deflector(self): :return: list of einstein radius of each lens-source pair. """ - self._theta_E_list = [] - for source in self.source: - self._theta_E_list.append(self._einstein_radius_deflector(source)) + if not hasattr(self, "_theta_E_list"): + self._theta_E_list = [] + for source in self.source: + self._theta_E_list.append(self._einstein_radius_deflector(source)) return self._theta_E_list def _einstein_radius_deflector(self, source): """Einstein radius, from SIS approximation (coming from velocity dispersion) without line-of-sight correction. + :param source: Source class instance. The redshift of this source is used in + the LensCosmo or LensModel. :return: einstein radius of a lens-source pair. """ - if not hasattr(self, "_theta_E"): - if self.deflector.redshift >= source.redshift: - self._theta_E = 0 - elif self.deflector.deflector_type in ["EPL"]: - _lens_cosmo = LensCosmo( - z_lens=float(self.deflector.redshift), - z_source=float(source.redshift), - cosmo=self.cosmo, - ) - self._theta_E = _lens_cosmo.sis_sigma_v2theta_E( - float(self.deflector.velocity_dispersion(cosmo=self.cosmo)) - ) - else: - # numerical solution for the Einstein radius - lens_model_list, kwargs_lens = self.deflector_mass_model_lenstronomy() - lens_model = LensModel(lens_model_list=lens_model_list, - z_lens=self.deflector_redshift, - z_source_convention=self.max_redshift_source_class.redshift, - multi_plane=False, - z_source=source.redshift) - lens_analysis = LensProfileAnalysis(lens_model=lens_model) - self._theta_E = lens_analysis.effective_einstein_radius( - kwargs_lens, r_min=1e-3, r_max=5e1, num_points=50 - ) + if self.deflector.redshift >= source.redshift: + self._theta_E = 0 + elif self.deflector.deflector_type in ["EPL"]: + _lens_cosmo = LensCosmo( + z_lens=float(self.deflector.redshift), + z_source=float(source.redshift), + cosmo=self.cosmo, + ) + self._theta_E = _lens_cosmo.sis_sigma_v2theta_E( + float(self.deflector.velocity_dispersion(cosmo=self.cosmo)) + ) + else: + # numerical solution for the Einstein radius + lens_model_list, kwargs_lens = self.deflector_mass_model_lenstronomy() + lens_model = LensModel(lens_model_list=lens_model_list, + z_lens=self.deflector_redshift, + z_source_convention=self.max_redshift_source_class.redshift, + multi_plane=False, + z_source=source.redshift) + lens_analysis = LensProfileAnalysis(lens_model=lens_model) + self._theta_E = lens_analysis.effective_einstein_radius( + kwargs_lens, r_min=1e-3, r_max=5e1, num_points=50 + ) return self._theta_E @property @@ -422,10 +431,11 @@ def einstein_radius(self): :return: list of Einstein radius [arc seconds] for each lens source pair. """ - theta_E_list = [] - for source in self.source: - theta_E_list.append(self._einstein_radius(source=source)) - return theta_E_list + if not hasattr(self, "_theta_E_corrected_list"): + self._theta_E_corrected_list = [] + for source in self.source: + self._theta_E_corrected_list.append(self._einstein_radius(source=source)) + return self._theta_E_corrected_list def _einstein_radius(self, source): """Einstein radius, from SIS approximation (coming from velocity dispersion) + @@ -673,26 +683,28 @@ def point_source_magnification(self): :return: list of signed magnification of point sources in same order as image positions. """ - self._ps_magnification_list = [] - for source in self.source: - self._ps_magnification_list.append( - self._point_source_magnification(source)) + if not hasattr(self, "_ps_magnification_list"): + self._ps_magnification_list = [] + for source in self.source: + self._ps_magnification_list.append( + self._point_source_magnification(source)) return self._ps_magnification_list def _point_source_magnification(self, source): """Macro-model magnification of a point source. This is for a single source. + :param source: Source class instance. The redshift of this source is used in + the LensModel. :return: signed magnification of a point source in same order as image positions """ - if not hasattr(self, "_ps_magnification"): - lens_model_list, kwargs_lens = self.deflector_mass_model_lenstronomy() - lensModel = LensModel(lens_model_list=lens_model_list, - z_lens=self.deflector_redshift, - z_source_convention=self.max_redshift_source_class.redshift, - multi_plane=False, - z_source=source.redshift) - img_x, img_y = self._point_source_image_positions(source) - self._ps_magnification = lensModel.magnification(img_x, img_y, kwargs_lens) + lens_model_list, kwargs_lens = self.deflector_mass_model_lenstronomy() + lensModel = LensModel(lens_model_list=lens_model_list, + z_lens=self.deflector_redshift, + z_source_convention=self.max_redshift_source_class.redshift, + multi_plane=False, + z_source=source.redshift) + img_x, img_y = self._point_source_image_positions(source) + self._ps_magnification = lensModel.magnification(img_x, img_y, kwargs_lens) return self._ps_magnification def extended_source_magnification(self): @@ -704,10 +716,11 @@ def extended_source_magnification(self): """ #TODO: add source redshift in ray_shooting. Wait for lenstronomy new version. - self._extended_source_magnification_list = [] - for index, source in enumerate(self.source): - self._extended_source_magnification_list.append( - self._extended_single_source_magnification(source, index)) + if not hasattr(self, "_extended_source_magnification_list"): + self._extended_source_magnification_list = [] + for index, source in enumerate(self.source): + self._extended_source_magnification_list.append( + self._extended_single_source_magnification(source, index)) return self._extended_source_magnification_list def _extended_single_source_magnification(self, source, source_index): @@ -719,49 +732,48 @@ def _extended_single_source_magnification(self, source, source_index): :param source_index: index of a source in source list. :return: integrated magnification factor of host magnitude """ - if not hasattr(self, "_extended_source_magnification"): - kwargs_model, kwargs_params = self.lenstronomy_kwargs(band=None) - _light_model_list = kwargs_model.get( - "source_light_model_list", [])[source_index] - kwargs_source_mag = [kwargs_params["kwargs_source"][source_index]] - if isinstance(_light_model_list, list): - light_model_list = _light_model_list - else: - light_model_list = [_light_model_list] - lightModel = LightModel( - light_model_list=light_model_list) - lensModel = LensModel( - lens_model_list=kwargs_model.get("lens_model_list", []), - z_lens=self.deflector_redshift, - z_source_convention=self.max_redshift_source_class.redshift, - multi_plane=False, - z_source=source.redshift - ) - theta_E = self._einstein_radius(source) - center_source = source.extended_source_position( - center_lens=self.deflector_position, draw_area=self.test_area - ) + kwargs_model, kwargs_params = self.lenstronomy_kwargs(band=None) + _light_model_list = kwargs_model.get( + "source_light_model_list", [])[source_index] + kwargs_source_mag = [kwargs_params["kwargs_source"][source_index]] + if isinstance(_light_model_list, list): + light_model_list = _light_model_list + else: + light_model_list = [_light_model_list] + lightModel = LightModel( + light_model_list=light_model_list) + lensModel = LensModel( + lens_model_list=kwargs_model.get("lens_model_list", []), + z_lens=self.deflector_redshift, + z_source_convention=self.max_redshift_source_class.redshift, + multi_plane=False, + z_source=source.redshift + ) + theta_E = self._einstein_radius(source) + center_source = source.extended_source_position( + center_lens=self.deflector_position, draw_area=self.test_area + ) - kwargs_source_amp = data_util.magnitude2amplitude( - lightModel, kwargs_source_mag, magnitude_zero_point=0 - ) + kwargs_source_amp = data_util.magnitude2amplitude( + lightModel, kwargs_source_mag, magnitude_zero_point=0 + ) - num_pix = 200 - delta_pix = theta_E * 4 / num_pix - x, y = util.make_grid(numPix=num_pix, deltapix=delta_pix) - x += center_source[0] - y += center_source[1] - beta_x, beta_y = lensModel.ray_shooting(x, y, kwargs_params["kwargs_lens"]) - flux_lensed = np.sum( - lightModel.surface_brightness(beta_x, beta_y, kwargs_source_amp) - ) - flux_no_lens = np.sum( - lightModel.surface_brightness(x, y, kwargs_source_amp) - ) - if flux_no_lens > 0: - self._extended_source_magnification = flux_lensed / flux_no_lens - else: - self._extended_source_magnification = 0 + num_pix = 200 + delta_pix = theta_E * 4 / num_pix + x, y = util.make_grid(numPix=num_pix, deltapix=delta_pix) + x += center_source[0] + y += center_source[1] + beta_x, beta_y = lensModel.ray_shooting(x, y, kwargs_params["kwargs_lens"]) + flux_lensed = np.sum( + lightModel.surface_brightness(beta_x, beta_y, kwargs_source_amp) + ) + flux_no_lens = np.sum( + lightModel.surface_brightness(x, y, kwargs_source_amp) + ) + if flux_no_lens > 0: + self._extended_source_magnification = flux_lensed / flux_no_lens + else: + self._extended_source_magnification = 0 return self._extended_source_magnification def lenstronomy_kwargs(self, band=None): From a99892e4467f00067c46592865ebcac638985d32 Mon Sep 17 00:00:00 2001 From: narayan Date: Wed, 6 Nov 2024 11:38:57 -0500 Subject: [PATCH 81/87] worked on Simon's comments. --- tests/test_image_simulation.py | 121 +++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) diff --git a/tests/test_image_simulation.py b/tests/test_image_simulation.py index 198fbd255..eadceeb73 100644 --- a/tests/test_image_simulation.py +++ b/tests/test_image_simulation.py @@ -379,6 +379,127 @@ def test_deflector_images_with_different_zeropoint(pes_lens_instance): assert len(lens_image_result_3) == 2 assert np.any(residual != 0) +class TestMultiSourceImageSimulation(object): + def setup_method(self): + self.cosmo = FlatLambdaCDM(H0=70, Om0=0.3) + path = os.path.dirname(__file__) + source_dict1 = Table.read( + os.path.join(path, "TestData/source_supernovae_new.fits"), format="fits" + ) + data={'ra_off':[-0.2524832112858584], + 'dec_off':[0.1394853307977928], + 'sep':[0.288450913482674], + 'z':[0.65], + 'R_d':[3.515342568459843], + 'R_s':[2.0681117132023721], + 'logMstar':[10.7699], + 'logSFR':[0.6924], + 'a0': [0.51747227], + 'a1': [0.32622826], + 'a_rot':[0.2952329149503528], + 'b0':[0.25262737], + 'b1':[0.27223456], + 'e':[0.3303168046302505], + 'ellipticity0': [0.33939099024025735], + 'ellipticity1': [0.0802206575082465], + 'mag_g': [22.936048], + 'mag_i':[21.78715], + 'mag_r':[22.503948], + 'n_sersic_0':[1.0], + 'n_sersic_1':[4.0], + 'w0':[0.907], + 'w1':[0.093], + 'e0_1':[0.14733325180101145], + 'e0_2':[0.09874724195027847], + 'e1_1':[0.03754887782202202], + 'e1_2':[0.025166403903583694], + 'angular_size0':[0.37156280037917327], + 'angular_size1':[0.29701108506340096]} + source_dict2 = Table(data) + deflector_dict = Table.read( + os.path.join(path, "TestData/deflector_supernovae_new.fits"), format="fits" + ) + self.source1 = Source( + source_dict=source_dict1, + cosmo=self.cosmo, + source_type="point_plus_extended", + light_profile="double_sersic", + lightcurve_time=np.linspace(-20, 100, 1000), + variability_model="light_curve", + kwargs_variability={"supernovae_lightcurve", "i"}, + sn_type="Ia", + sn_absolute_mag_band="bessellb", + sn_absolute_zpsys="ab", + + ) + self.source2 = Source( + source_dict=source_dict2, + cosmo=self.cosmo, + source_type="point_plus_extended", + light_profile="double_sersic", + lightcurve_time=np.linspace(-20, 100, 1000), + variability_model="light_curve", + kwargs_variability={"supernovae_lightcurve", "i"}, + sn_type="Ia", + sn_absolute_mag_band="bessellb", + sn_absolute_zpsys="ab", + + ) + self.deflector = Deflector( + deflector_type="EPL", + deflector_dict=deflector_dict, + ) + lens_class1 = Lens( + deflector_class=self.deflector, + source_class=self.source1, + cosmo=self.cosmo, + ) + lens_class2 = Lens( + deflector_class=self.deflector, + source_class=self.source2, + cosmo=self.cosmo, + ) + lens_class3 = Lens( + deflector_class=self.deflector, + source_class=[self.source1, self.source2], + cosmo=self.cosmo, + ) + path = os.path.dirname(__file__) + psf_kernel_single = np.load( + os.path.join(path, "TestData/psf_kernels_for_image_1.npy")) + image1 = lens_image( + lens_class=lens_class1, + band="i", + mag_zero_point=27, + num_pix=64, + psf_kernel=psf_kernel_single, + transform_pix2angle=np.array([[0.2, 0], [0, 0.2]]), + exposure_time=30, + t_obs=10, + ) + image2 = lens_image( + lens_class=lens_class2, + band="i", + mag_zero_point=27, + num_pix=64, + psf_kernel=psf_kernel_single, + transform_pix2angle=np.array([[0.2, 0], [0, 0.2]]), + exposure_time=30, + t_obs=10, + ) + image3 = lens_image( + lens_class=lens_class3, + band="i", + mag_zero_point=27, + num_pix=64, + psf_kernel=psf_kernel_single, + transform_pix2angle=np.array([[0.2, 0], [0, 0.2]]), + exposure_time=30, + t_obs=10, + ) + combined_image = image1+image2 + assert image3==combined_image + if __name__ == "__main__": pytest.main() From 8020a8d3e4ac007c6b6347324ccda1a0d62dc01f Mon Sep 17 00:00:00 2001 From: narayan Date: Wed, 6 Nov 2024 11:42:53 -0500 Subject: [PATCH 82/87] worked on Simon's comments. --- slsim/lens.py | 1 - 1 file changed, 1 deletion(-) diff --git a/slsim/lens.py b/slsim/lens.py index 12cb1d0d9..d0e855ac8 100644 --- a/slsim/lens.py +++ b/slsim/lens.py @@ -797,7 +797,6 @@ def lenstronomy_kwargs(self, band=None): kwargs_model["lens_redshift_list"] = [ self.deflector_redshift]*len(lens_mass_model_list) kwargs_model["z_lens"] = self.deflector_redshift - kwargs_model["z_source"] = self.max_redshift_source_class.redshift kwargs_model["source_redshift_list"] = self.source_redshift_list kwargs_model["z_source_convention"]= self.max_redshift_source_class.redshift kwargs_model["cosmo"] = self.cosmo From 14953e4caef4d741064d615999515623e19b5819 Mon Sep 17 00:00:00 2001 From: narayan Date: Wed, 6 Nov 2024 11:54:09 -0500 Subject: [PATCH 83/87] worked on Simon's comments. --- slsim/lens.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/slsim/lens.py b/slsim/lens.py index d0e855ac8..ca6460939 100644 --- a/slsim/lens.py +++ b/slsim/lens.py @@ -243,17 +243,17 @@ def validity_test(self, :type mag_arc_limit: dict with key of bands and values of magnitude limits :return: boolean """ - validity_boolean = [] + validity_results = {} for index, source in enumerate(self.source): - validity_boolean.append(self._validity_test(source, + validity_results[index] = self._validity_test(source, min_image_separation=min_image_separation, max_image_separation=max_image_separation, mag_arc_limit=mag_arc_limit, - source_index=index)) - if np.all(validity_boolean) == True: - return True + source_index=index) + if len(validity_results)==1: + return validity_results[0] else: - return False + return validity_results def _validity_test( self, From 1af1b2efba4db42b12ccb818b338e2c449fe2553 Mon Sep 17 00:00:00 2001 From: narayan Date: Wed, 6 Nov 2024 11:57:54 -0500 Subject: [PATCH 84/87] minor change. --- slsim/lens.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slsim/lens.py b/slsim/lens.py index ca6460939..d75d5e1e9 100644 --- a/slsim/lens.py +++ b/slsim/lens.py @@ -241,7 +241,7 @@ def validity_test(self, :param mag_arc_limit: dictionary with key of bands and values of magnitude limits of integrated lensed arc :type mag_arc_limit: dict with key of bands and values of magnitude limits - :return: boolean + :return: A boolean or dict of boolean. """ validity_results = {} for index, source in enumerate(self.source): From 720ad1f690d224e05947160afd98cb01e40fef6c Mon Sep 17 00:00:00 2001 From: narayan Date: Wed, 6 Nov 2024 13:35:50 -0500 Subject: [PATCH 85/87] minor change. --- tests/test_lens.py | 4 ++++ tests/test_lens_pop.py | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/test_lens.py b/tests/test_lens.py index 3627b880d..d5c39bd57 100644 --- a/tests/test_lens.py +++ b/tests/test_lens.py @@ -629,6 +629,10 @@ def setup_method(self): #Test multisource image observation time assert image_observation_time1[0] == image_observation_time3[0] assert image_observation_time2[0] == image_observation_time3[1] + assert lens_class1.einstein_radius_deflector[0] == lens_class3.einstein_radius_deflector[0] + assert lens_class1.einstein_radius[0] == lens_class3.einstein_radius[0] + assert len(lens_class3.image_observer_times(t_obs=10)) == 2 + if __name__ == "__main__": pytest.main() diff --git a/tests/test_lens_pop.py b/tests/test_lens_pop.py index ed679ba38..dc292c593 100644 --- a/tests/test_lens_pop.py +++ b/tests/test_lens_pop.py @@ -60,7 +60,11 @@ def create_lens_pop_instance(return_kext=False): def gg_lens_pop_instance(): # Create LensPop instance without return_kext return create_lens_pop_instance(return_kext=False) - +def test_draw_population(gg_lens_pop_instance): + lens_pop=gg_lens_pop_instance + kwargs_lens_cuts = {} + lens_population = lens_pop.draw_population(kwargs_lens_cuts) + assert len(lens_population) <= 25 def test_pes_lens_pop_instance(): cosmo = FlatLambdaCDM(H0=70, Om0=0.3) From 5d276677cab1572effcacc2d16a87b6f48ac02d1 Mon Sep 17 00:00:00 2001 From: narayan Date: Wed, 6 Nov 2024 15:00:04 -0500 Subject: [PATCH 86/87] minor change. --- tests/test_lens_pop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_lens_pop.py b/tests/test_lens_pop.py index dc292c593..42b7f6e75 100644 --- a/tests/test_lens_pop.py +++ b/tests/test_lens_pop.py @@ -64,7 +64,7 @@ def test_draw_population(gg_lens_pop_instance): lens_pop=gg_lens_pop_instance kwargs_lens_cuts = {} lens_population = lens_pop.draw_population(kwargs_lens_cuts) - assert len(lens_population) <= 25 + assert len(lens_population) <= 40 def test_pes_lens_pop_instance(): cosmo = FlatLambdaCDM(H0=70, Om0=0.3) From a819578b4780656b44ac8d0ea90609b4dd248ac6 Mon Sep 17 00:00:00 2001 From: narayan Date: Wed, 6 Nov 2024 18:10:32 -0500 Subject: [PATCH 87/87] minor change. --- slsim/lens.py | 1 + 1 file changed, 1 insertion(+) diff --git a/slsim/lens.py b/slsim/lens.py index d75d5e1e9..a01f85b1b 100644 --- a/slsim/lens.py +++ b/slsim/lens.py @@ -271,6 +271,7 @@ def _validity_test( :param mag_arc_limit: dictionary with key of bands and values of magnitude limits of integrated lensed arc :type mag_arc_limit: dict with key of bands and values of magnitude limits + :param source_index: index of a source in source list. :return: boolean """