diff --git a/slsim/Sources/Events/BNSMerger/kilonova.py b/slsim/Sources/Events/BNSMerger/kilonova.py new file mode 100644 index 000000000..bfb5fce66 --- /dev/null +++ b/slsim/Sources/Events/BNSMerger/kilonova.py @@ -0,0 +1,169 @@ +import numpy as np + +if not hasattr(np, "trapezoid"): + np.trapezoid = np.trapz + +from astropy import cosmology +from redback.transient_models import kilonova_models + + +class Kilonova: + """Class for initializing a kilonova light curve model. + + If modeldir is provided, external kilonova model files are expected. + This option is currently not supported. If modeldir is not provided, + the model is retrieved from Redback's built-in kilonova models. By + default, the MOSFiT-based kilonova model is used. Information about + Redback can be found at + https://redback.readthedocs.io/en/latest/. + + Following the GW170817 three-component convention, components 1, 2, + and 3 can represent the blue, purple, and red ejecta components when + assigned low, intermediate, and high opacities, respectively. This + correspondence is not enforced, and users may specify the parameters + of each component independently. + """ + + def __init__( + self, + redshift, + mej_1, + mej_2, + mej_3, + vej_1, + vej_2, + vej_3, + kappa_1, + kappa_2, + kappa_3, + temperature_floor_1, + temperature_floor_2, + temperature_floor_3, + model_name="mosfit_kilonova", + kappa_gamma=10, + mag_zpsys="AB", + cosmo=cosmology.FlatLambdaCDM(H0=70, Om0=0.3), + modeldir=None, + **kwargs, + ): + """ + :param redshift: The redshift of the kilonova source. + :type redshift: float + + :param mej_1: Ejecta mass of model component 1 in [M_sun], which + sets the amount of radiating material and affecting the photon diffusion + timescale. + :type mej_1: float + :param mej_2: Ejecta mass of model component 2, with the same + definition and units as ``mej_1``. + :type mej_2: float + :param mej_3: Ejecta mass of model component 3, with the same + definition and units as ``mej_1``. + :type mej_3: float + :param vej_1: Expansion velocity of model component 1 in units of the + speed of light [c], affecting its expansion and photon diffusion + timescale. + :type vej_1: float + :param vej_2: Expansion velocity of model component 2, with the same + definition and units as ``vej_1``. + :type vej_2: float + :param vej_3: Expansion velocity of model component 3, with the same + definition and units as ``vej_1``. + :type vej_3: float + :param kappa_1: Effective gray opacity of model component 1 in + [cm^2 g^-1], controlling how readily radiation escapes from the + ejecta. + :type kappa_1: float + :param kappa_2: Effective gray opacity of model component 2, with the + same definition and units as ``kappa_1``. + :type kappa_2: float + :param kappa_3: Effective gray opacity of model component 3, with the + same definition and units as ``kappa_1``. + :type kappa_3: float + :param temperature_floor_1: Minimum effective photospheric temperature of model + component 1 in [K], affecting its late-time spectral evolution. + :type temperature_floor_1: float + :param temperature_floor_2: Minimum effective photospheric temperature of model + component 2, with the same definition and units as ``temperature_floor_1``. + :type temperature_floor_2: float + :param temperature_floor_3: Minimum effective photospheric temperature of model + component 3, with the same definition and units as ``temperature_floor_1``. + :type temperature_floor_3: float + + :param model_name: The kilonova light curve model to be used. If not provided, + the default model is the MOSFiT-based kilonova model. + :type model_name: str + :param kappa_gamma: Gamma-ray opacity shared by all three model components + in [cm^2 g^-1], controlling the trapping and escape of high-energy radiation + from radioactive decay. + :type kappa_gamma: float + :param mag_zpsys: Optional, AB or Vega (AB default). + :type mag_zpsys: str + :param cosmo: Cosmology for luminosity distance calculation. + :type cosmo: `~astropy.cosmology` + :param modeldir: Directory including files for external kilonova models. + :type modeldir: str or None + :param kwargs: Additional keyword arguments passed to the Redback kilonova model. + :type kwargs: dict + """ + + if modeldir is not None: + raise NotImplementedError( + "Only built-in Redback kilonova models are currently supported. " + "External kilonova model files are not supported." + ) + + if not hasattr(kilonova_models, model_name): + raise ValueError( + f"Unsupported kilonova model '{model_name}'. " + "The model must be available in " + "redback.transient_models.kilonova_models." + ) + + self._model = getattr(kilonova_models, model_name) + + self._model_name = model_name + self._redshift = redshift + self._mag_zpsys = mag_zpsys + self._cosmo = cosmo + self._kwargs = kwargs + + self._model_parameters = { + "mej_1": mej_1, + "mej_2": mej_2, + "mej_3": mej_3, + "vej_1": vej_1, + "vej_2": vej_2, + "vej_3": vej_3, + "kappa_1": kappa_1, + "kappa_2": kappa_2, + "kappa_3": kappa_3, + "temperature_floor_1": temperature_floor_1, + "temperature_floor_2": temperature_floor_2, + "temperature_floor_3": temperature_floor_3, + "kappa_gamma": kappa_gamma, + } + + def get_apparent_magnitude(self, time, band, zpsys="AB"): + """Function to return apparent magnitude of a kilonova for a given band + and time. + + :param time: The observer-frame time array to evaluate the model + (in days) + :type time: array-like + :param band: The band to evaluate the model over. + :type band: str or list + :param zpsys: Optional, AB or Vega (AB default) + :type zpsys: str + :return: magnitude of source + """ + + return self._model( + time=time, + redshift=self._redshift, + bands=band, + output_format="magnitude", + cosmology=self._cosmo, + **self._model_parameters, + **self._kwargs, + ) diff --git a/slsim/Sources/SourceTypes/kilonova_event.py b/slsim/Sources/SourceTypes/kilonova_event.py new file mode 100644 index 000000000..1a6b06246 --- /dev/null +++ b/slsim/Sources/SourceTypes/kilonova_event.py @@ -0,0 +1,158 @@ +import warnings +import numpy as np +from slsim.Sources.SourceTypes.source_base import SourceBase +from slsim.Sources.Events.BNSMerger.kilonova import Kilonova +from slsim.ImageSimulation.image_quality_lenstronomy import ( + get_all_supported_bands, + get_sncosmo_filtername, +) + + +class KilonovaEvent(SourceBase): + """A class to manage a BNS merger.""" + + def __init__( + self, + lightcurve_time, + variability_model, + model_name="mosfit_kilonova", + mag_zpsys="AB", + modeldir=None, + kwargs_variability=None, + kwargs_kilonova=None, + cosmo=None, + **kwargs, + ): + """ + :param lightcurve_time: Observation time array for the light curve in [days]. + :type lightcurve_time: array-like + :param variability_model: Keyword for the variability model to be used. This is an + input for the Variability class. + :type variability_model: str + :param model_name: Kilonova light curve model to be used. If not provided, the + default model is the MOSFiT-based kilonova model. + :type model_name: str + :param mag_zpsys: Optional, AB or Vega (AB default). + :type mag_zpsys: str + :param modeldir: Directory including files for external kilonova models. This + option is currently not supported. + :type modeldir: str or None + :param kwargs_variability: List containing the variability keyword and the + bands for which the light curve should be generated. + :type kwargs_variability: list or None + :param kwargs_kilonova: Keyword arguments passed to the Kilonova class. This may + include ejecta_mass in [solar masses], ejecta_velocity in [c], opacity in + [cm^2 g^-1], temperature_floor in [K], and kappa_gamma in [cm^2 g^-1]. + :type kwargs_kilonova: dict or None + :param cosmo: Astropy cosmology instance. + :type cosmo: `~astropy.cosmology` + :param kwargs: Keyword arguments passed to the SourceBase class. This may contain + source properties such as redshift and offsets from the host galaxy in [arcsec]. + :type kwargs: dict + """ + super().__init__( + extended_source=False, + point_source=True, + cosmo=cosmo, + variability_model=variability_model, + **kwargs, + ) + self.name = "BNS" + self._variability_computed = False + self._kwargs_variability = kwargs_variability + self._lightcurve_time = lightcurve_time + + self._model_name = model_name + self._mag_zpsys = mag_zpsys + self._modeldir = modeldir + + if kwargs_kilonova is None: + self._kwargs_kilonova = {} + else: + self._kwargs_kilonova = kwargs_kilonova + + @property + def light_curve(self): + """Provides lightcurves of a bns merger in each band.""" + if self._kwargs_variability is not None: + kwargs_variab_extracted = {} + if self._cosmo is None: + raise ValueError( + "Cosmology cannot be None for BNSMerger class. Please" + "provide a suitable astropy cosmology." + ) + else: + # Initialize BNS/Kilonova light curve model + lightcurve_class = Kilonova( + redshift=self._z, + model_name=self._model_name, + mag_zpsys=self._mag_zpsys, + cosmo=self._cosmo, + modeldir=self._modeldir, + **self._kwargs_kilonova, + ) + self._lightcurve_class = lightcurve_class + + supported_bands = get_all_supported_bands() + provided_bands = set(supported_bands) & set(self._kwargs_variability) + + for element in provided_bands: + name = "ps_mag_" + element + times = self._lightcurve_time + + # Convert SLSim short band labels, such as "i" and "r", to registered + # filter names required by Redback, such as "lssti" and "lsstr". + # We reuse the existing SLSim/SNcosmo filter-name helper for this mapping. + provided_band = get_sncosmo_filtername(element) + + try: + magnitudes = lightcurve_class.get_apparent_magnitude( + time=times, + band=provided_band, + zpsys=self._mag_zpsys, + ) + # make sure before and after the event, the flux is zero + magnitudes = np.append(np.inf, magnitudes) + magnitudes = np.append(magnitudes, np.inf) + padded_times = np.append(times[0] - (times[1] - times[0]), times) + padded_times = np.append(padded_times, 2 * times[-1] - times[-2]) + except Exception as e: + warnings.warn( + f"Skipping band '{provided_band}': Failed to generate lightcurve. " + f"(Error: {e})", + UserWarning, + ) + continue + + if name not in self.source_dict: + self.source_dict[name] = float(min(magnitudes)) + + kwargs_variab_extracted[element] = { + "MJD": padded_times, + name: magnitudes, + } + else: + kwargs_variab_extracted = {} + + self._variability_computed = True + return kwargs_variab_extracted + + def point_source_magnitude(self, band, image_observation_times=None): + """Get the magnitude of the BNS/kilonova point source in a specific + band. + + :param band: Imaging band. + :type band: str + :param image_observation_times: Image observation times in + [days]. If None, takes the peak magnitude. + :type image_observation_times: array-like or None + :return: Magnitude of the point source in the specified band. + :rtype: float or array-like + """ + + if not self._variability_computed: + self._kwargs_variability_model = self.light_curve + + return super().point_source_magnitude( + band=band, image_observation_times=image_observation_times + ) diff --git a/slsim/Sources/SourceTypes/point_source.py b/slsim/Sources/SourceTypes/point_source.py index 50a36a192..737fad935 100644 --- a/slsim/Sources/SourceTypes/point_source.py +++ b/slsim/Sources/SourceTypes/point_source.py @@ -1,4 +1,4 @@ -_SUPPORTED_POINT_SOURCES = ["supernova", "quasar", "general_lightcurve"] +_SUPPORTED_POINT_SOURCES = ["supernova", "quasar", "general_lightcurve", "kilonova"] class PointSource(object): @@ -25,6 +25,10 @@ def __init__(self, source_type, **source_dict): from slsim.Sources.SourceTypes.general_lightcurve import GeneralLightCurve self._point_source = GeneralLightCurve(**source_dict) + elif source_type in ["kilonova"]: + from slsim.Sources.SourceTypes.kilonova_event import KilonovaEvent + + self._point_source = KilonovaEvent(**source_dict) else: raise ValueError( "Point source type %s not supported. Chose among %s." diff --git a/slsim/Sources/source.py b/slsim/Sources/source.py index 920655722..68c14912e 100644 --- a/slsim/Sources/source.py +++ b/slsim/Sources/source.py @@ -2,7 +2,7 @@ from slsim.Sources.SourceTypes.source_base import SourceBase from copy import deepcopy -_SUPPORTED_POINT_SOURCES = ["supernova", "quasar", "general_lightcurve"] +_SUPPORTED_POINT_SOURCES = ["supernova", "quasar", "general_lightcurve", "kilonova"] _SUPPORTED_EXTENDED_SOURCES = [ "single_sersic", "double_sersic", @@ -27,7 +27,7 @@ def __init__( extended source types are 'single_sersic', 'double_sersic', 'catalog_source', and 'interpolated'. :type extended_source_type: str or None :param point_source_type: Keyword to specify type of point source. Supported point - source types are 'supernova', 'quasar', and 'general_lightcurve'. + source types are 'supernova', 'quasar', 'general_lightcurve', and 'kilonova'. :type point_source_type: str or None :param source_dict: Source properties. Can be a dictionary or an Astropy table. For a detailed description of this dictionary, please see the documentation for @@ -69,6 +69,10 @@ def __init__( from slsim.Sources.SourceTypes.general_lightcurve import GeneralLightCurve self._source = GeneralLightCurve(**source_dict) + elif source_type in ["kilonova"]: + from slsim.Sources.SourceTypes.kilonova_event import KilonovaEvent + + self._source = KilonovaEvent(**source_dict) # extended sources elif source_type in ["single_sersic"]: diff --git a/test_requirements.txt b/test_requirements.txt index 565d4650d..0b33ae1d6 100644 --- a/test_requirements.txt +++ b/test_requirements.txt @@ -7,3 +7,4 @@ pyhalo<=1.4.6 mcfit sncosmo stpsf +redback>=1.17.0 \ No newline at end of file diff --git a/tests/test_Lenses/test_LensPopulation/test_lens_pop.py b/tests/test_Lenses/test_LensPopulation/test_lens_pop.py index 5681c7a05..5d5fb7585 100644 --- a/tests/test_Lenses/test_LensPopulation/test_lens_pop.py +++ b/tests/test_Lenses/test_LensPopulation/test_lens_pop.py @@ -448,6 +448,91 @@ def test_supernovae_lens_pop_instance(): ) +def test_kilonova_lens_pop_instance(): + cosmo = FlatLambdaCDM(H0=70, Om0=0.3) + sky_area_1 = Quantity(value=0.1, unit="deg2") + sky_area_pop = Quantity(value=0.1, unit="deg2") + + kwargs_deflector_cut = {"band": "g", "band_max": 23, "z_min": 0.01, "z_max": 2.5} + kwargs_source_cut = {"z_min": 0.1, "z_max": 5.0} + + time_range = np.linspace(0.1, 30, 50) + + lens_galaxies_1 = GalaxyDeflectors( + red_galaxy_list=galaxy_simulation_pipeline.red_galaxies, + kwargs_cut=kwargs_deflector_cut, + kwargs_mass2light={}, + cosmo=cosmo, + sky_area=sky_area_1, + ) + + kilonova_data_1 = Table( + { + "z": [0.8, 1.0, 1.2], + "ra_off": [0.001, 0.001, 0.001], + "dec_off": [0.005, 0.005, 0.005], + "ps_mag_i": [28.3, 28.5, 28.7], + "ps_mag_r": [28.0, 28.2, 28.4], + } + ) + + kwargs_kilonova = { + "ejecta_mass": [0.01, 0.02, 0.03], + "ejecta_velocity": [0.1, 0.2, 0.3], + "opacity": [0.5, 3.0, 10.0], + "temperature_floor": [5000, 4000, 3000], + "kappa_gamma": 10, + } + + point_source_kwargs = { + "variability_model": "light_curve", + "kwargs_variability": ["kilonova_lightcurve", "i", "r"], + "lightcurve_time": time_range, + "model_name": "mosfit_kilonova", + "mag_zpsys": "AB", + "modeldir": None, + "kwargs_kilonova": kwargs_kilonova, + } + source_galaxies_1 = PointPlusExtendedSources( + point_plus_extended_sources_list=kilonova_data_1, + cosmo=cosmo, + sky_area=sky_area_1, + kwargs_cut=kwargs_source_cut, + point_source_type="kilonova", + extended_source_type=None, + joint_point_source_kwargs=point_source_kwargs, + ) + + ps_lens_pop_1 = LensPop( + deflector_population=lens_galaxies_1, + source_population=source_galaxies_1, + cosmo=cosmo, + sky_area=sky_area_pop, + use_jax=use_jax, + ) + # drawing population + kwargs_lens_cuts = {} + ps_lens_population_1 = ps_lens_pop_1.draw_population( + speed_factor=1, kwargs_lens_cuts=kwargs_lens_cuts + ) + ps_lens_population_1_speed = ps_lens_pop_1.draw_population( + speed_factor=10, kwargs_lens_cuts=kwargs_lens_cuts + ) + kwargs_lens_cut = {} + ps_lens_class = ps_lens_pop_1.select_lens_at_random(**kwargs_lens_cut) + assert isinstance(ps_lens_class, Lens) + assert ps_lens_class._source[0].source_type == "point_source" + assert "z" in kilonova_data_1.colnames + assert abs(len(ps_lens_population_1) - len(ps_lens_population_1_speed)) <= 12 + with pytest.raises(ValueError): + LensPop( + deflector_population=lens_galaxies_1, + source_population=source_galaxies_1, + cosmo=cosmo, + use_jax=use_jax, + ) + + def test_num_lenses_and_sources(gg_lens_pop_instance): num_lenses = gg_lens_pop_instance.deflector_number num_sources = gg_lens_pop_instance.source_number diff --git a/tests/test_Plots/test_plot_functions.py b/tests/test_Plots/test_plot_functions.py index 86024d175..7cdf6d19f 100644 --- a/tests/test_Plots/test_plot_functions.py +++ b/tests/test_Plots/test_plot_functions.py @@ -88,6 +88,8 @@ def quasar_lens_pop_instance(): def test_create_image_montage_from_image_list(quasar_lens_pop_instance): + plt.rcParams["text.usetex"] = False + lens_class = quasar_lens_pop_instance image = sharp_image( lens_class=lens_class, diff --git a/tests/test_Sources/test_Events/test_BNSMerger/test_kilonova.py b/tests/test_Sources/test_Events/test_BNSMerger/test_kilonova.py new file mode 100644 index 000000000..5d917054a --- /dev/null +++ b/tests/test_Sources/test_Events/test_BNSMerger/test_kilonova.py @@ -0,0 +1,80 @@ +import numpy as np +from slsim.Sources.Events.BNSMerger.kilonova import Kilonova +import numpy.testing as npt +import pytest + + +@pytest.fixture +def kilonova_parameters(): + return { + "mej_1": 0.01, + "mej_2": 0.02, + "mej_3": 0.03, + "vej_1": 0.1, + "vej_2": 0.2, + "vej_3": 0.3, + "kappa_1": 0.5, + "kappa_2": 3.0, + "kappa_3": 10.0, + "temperature_floor_1": 5000, + "temperature_floor_2": 4000, + "temperature_floor_3": 3000, + "kappa_gamma": 10, + } + + +@pytest.fixture +def kilonova_class(kilonova_parameters): + KN = Kilonova( + redshift=0.1, + model_name="mosfit_kilonova", + mag_zpsys="AB", + dense_resolution=50, + **kilonova_parameters, + ) + + return KN + + +def test_kilonova_mag(kilonova_class): + time = np.array([0.5, 1.0, 2.0]) + mag = kilonova_class.get_apparent_magnitude(time=time, band="lsstr") + + npt.assert_equal(np.shape(mag), np.shape(time)) + npt.assert_(np.all(np.isfinite(mag))) + npt.assert_(np.all(mag > 0)) + + +def test_kilonova_missing_parameters(kilonova_parameters): + # Test that omitting a required model parameter raises an error. + incomplete_parameters = kilonova_parameters.copy() + incomplete_parameters.pop("mej_1") + + with pytest.raises(TypeError): + Kilonova( + redshift=0.1, + **incomplete_parameters, + ) + + +def test_kilonova_invalid_model_name(kilonova_parameters): + # Test that an unavailable Redback model name raises an error. + with pytest.raises(ValueError): + Kilonova( + redshift=0.1, + model_name="not_a_kilonova_model", + **kilonova_parameters, + ) + + +def test_kilonova_external_modeldir_not_supported(kilonova_parameters): + with pytest.raises(NotImplementedError): + Kilonova( + redshift=0.1, + modeldir="some/path", + **kilonova_parameters, + ) + + +if __name__ == "__main__": + pytest.main() diff --git a/tests/test_Sources/test_SourceTypes/test_kilonova_event.py b/tests/test_Sources/test_SourceTypes/test_kilonova_event.py new file mode 100644 index 000000000..381d66224 --- /dev/null +++ b/tests/test_Sources/test_SourceTypes/test_kilonova_event.py @@ -0,0 +1,182 @@ +from slsim.Sources.Events.BNSMerger.kilonova import Kilonova +from slsim.Sources.SourceTypes.kilonova_event import KilonovaEvent +import slsim.ImageSimulation.image_quality_lenstronomy as iql +import numpy as np +import numpy.testing as npt +import pytest +from astropy import cosmology + + +class TestKilonovaEvent(object): + def setup_method(self): + self.cosmo = cosmology.FlatLambdaCDM(H0=70, Om0=0.3) + self.source_dict = {"z": 0.8, "ra_off": 0.001, "dec_off": 0.005} + + source_dict2 = { + "z": 0.8, + "ra_off": 0.001, + "dec_off": 0.005, + "ps_mag_i": 20, + } + + source_dict3 = { + "z": 0.8, + "ra_off": 0.001, + "dec_off": 0.005, + "MJD": [0, 2, 3, 4, 5, 6], + "ps_mag_i": [21, 20, 19, 21, 22, 23], + } + + self.kwargs_kilonova = { + "mej_1": 0.01, + "mej_2": 0.02, + "mej_3": 0.03, + "vej_1": 0.1, + "vej_2": 0.2, + "vej_3": 0.3, + "kappa_1": 0.5, + "kappa_2": 3.0, + "kappa_3": 10.0, + "temperature_floor_1": 5000, + "temperature_floor_2": 4000, + "temperature_floor_3": 3000, + "kappa_gamma": 10, + } + + kwargs_bns = { + # model type + "model_type": "bns_merger", + "variability_model": "light_curve", + "kwargs_variability": ["bns_lightcurve", "i", "r"], + "lightcurve_time": np.linspace(0.1, 10, 50), + "model_name": "mosfit_kilonova", + "mag_zpsys": "AB", + "modeldir": None, + "kwargs_kilonova": self.kwargs_kilonova, + } + + kwargs_bns_none = { + "model_type": "bns_merger", + "variability_model": "light_curve", + "kwargs_variability": None, + "lightcurve_time": np.linspace(0.1, 10, 50), + "model_name": "mosfit_kilonova", + "mag_zpsys": "AB", + "modeldir": None, + "kwargs_kilonova": self.kwargs_kilonova, + } + + self.source = KilonovaEvent(cosmo=self.cosmo, **kwargs_bns, **self.source_dict) + self.source_none = KilonovaEvent( + cosmo=self.cosmo, **kwargs_bns_none, **source_dict2 + ) + self.source_cosmo_error = KilonovaEvent( + cosmo=None, **kwargs_bns, **self.source_dict + ) + self.source_light_curve = KilonovaEvent( + cosmo=self.cosmo, **kwargs_bns_none, **source_dict3 + ) + + def test_light_curve(self): + light_curve = self.source.light_curve + light_curve_none = self.source_none.light_curve + + # Check that the non-band parameter is successfully ignored. + assert "bns_lightcurve" not in light_curve.keys() + + assert "i" in light_curve.keys() + assert "r" in light_curve.keys() + assert "MJD" in light_curve["i"].keys() + assert "ps_mag_i" in light_curve["i"].keys() + assert "MJD" in light_curve["r"].keys() + assert "ps_mag_r" in light_curve["r"].keys() + assert len(light_curve["i"]["MJD"]) == 52 + assert len(light_curve["i"]["ps_mag_i"]) == 52 + + assert not light_curve_none + + with pytest.raises(ValueError): + self.source_cosmo_error.light_curve + + # _lightcurve_class must be stored as a Kilonova instance. + assert isinstance(self.source._lightcurve_class, Kilonova) + + # Check that kwargs_kilonova is passed into the Kilonova class. + assert self.source._lightcurve_class._model_parameters["mej_1"] == 0.01 + assert self.source._lightcurve_class._model_parameters["mej_2"] == 0.02 + assert self.source._lightcurve_class._model_parameters["mej_3"] == 0.03 + assert self.source._lightcurve_class._model_parameters["vej_1"] == 0.1 + assert self.source._lightcurve_class._model_parameters["vej_2"] == 0.2 + assert self.source._lightcurve_class._model_parameters["vej_3"] == 0.3 + assert self.source._lightcurve_class._model_parameters["kappa_1"] == 0.5 + assert self.source._lightcurve_class._model_parameters["kappa_2"] == 3.0 + assert self.source._lightcurve_class._model_parameters["kappa_3"] == 10.0 + assert ( + self.source._lightcurve_class._model_parameters["temperature_floor_1"] + == 5000 + ) + assert ( + self.source._lightcurve_class._model_parameters["temperature_floor_2"] + == 4000 + ) + assert ( + self.source._lightcurve_class._model_parameters["temperature_floor_3"] + == 3000 + ) + assert self.source._lightcurve_class._model_parameters["kappa_gamma"] == 10 + + # Check that the flux is zero before and after the modeled light curve. + assert np.isinf(light_curve["i"]["ps_mag_i"][0]) + assert np.isinf(light_curve["i"]["ps_mag_i"][-1]) + + # test specific values of a model lightcurve + npt.assert_almost_equal( + light_curve["i"]["ps_mag_i"][1], 30.52057182997839, decimal=5 + ) + npt.assert_almost_equal( + np.min(light_curve["i"]["ps_mag_i"]), 28.330211764355443, decimal=5 + ) + npt.assert_almost_equal( + light_curve["i"]["ps_mag_i"][-2], 31.496458821405547, decimal=5 + ) + + def test_light_curve_warning(self): + """Test that a UserWarning is raised when lightcurve generation + fails.""" + + class DummyObs: + def __init__(self, band, **kwargs): + pass + + def kwargs_single_band(self): + return {} + + iql.register_observatory( + "DummyBNSObs", DummyObs, bands=["unregistered_bns_band"] + ) + + self.source._kwargs_variability = [ + "bns_lightcurve", + "unregistered_bns_band", + ] + + with pytest.warns(UserWarning, match="Failed to generate lightcurve"): + failed_light_curve = self.source.light_curve + + assert failed_light_curve == {} + + def test_point_source_magnitude(self): + assert self.source.point_source_magnitude("i") is not None + + with pytest.raises(ValueError): + self.source.point_source_magnitude("g") + + with pytest.raises(ValueError): + self.source_none.point_source_magnitude("i", image_observation_times=10) + + assert self.source_none.point_source_magnitude("i") == 20 + assert self.source_light_curve.point_source_magnitude("i") == 21 + + +if __name__ == "__main__": + pytest.main()