.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "auto_examples/simulating/plot_multiband_too_followup.py" .. LINE NUMBERS ARE GIVEN BELOW. .. only:: html .. note:: :class: sphx-glr-download-link-note :ref:`Go to the end ` to download the full example code. .. rst-class:: sphx-glr-example-title .. _sphx_glr_auto_examples_simulating_plot_multiband_too_followup.py: Multi-Band ToO Follow-up =========================================== The **primary design** scope of ``uvex-transients`` is to simulate yields from UVEX's various surveys; however, there will certainly be cases when UVEX observes sources with corresponding OIR data from ZTF / Rubin / other ground based surveys. In some cases, this may be serendipetous, but in others, it may be because the sources was discovered and classified by a ground based observatory and a TOO was triggered on UVEX. In this example, we'll show off a simulation of this sort of scenario: Rubin gets on target first and starts a multi-band follow-up campaign of its own, then UVEX is triggered and joins in with its two UV bands. .. GENERATED FROM PYTHON SOURCE LINES 15-44 .. code-block:: Python import numpy as np from astropy import units as u from astropy.coordinates import SkyCoord from astropy.table import vstack from m4opt.missions import rubin, uvex from m4opt.synphot.background import GalacticBackground, SkyBackground from matplotlib import pyplot as plt from uvex_transients.dust import dust_map, log_attenuation, resolve_ebv from uvex_transients.transients.TDEs import TidalDisruptionEvent from uvex_transients.utils.plotting import get_band_color, plot_band_light_curve, resolve_fig_axes, set_plot_style set_plot_style() # Configure the transient and its parameters. tde = TidalDisruptionEvent() coord = SkyCoord(ra=195.3 * u.deg, dec=27.8 * u.deg) redshift = 0.2 luminosity_distance = tde.cosmology.luminosity_distance(redshift) # Determine the redenning from the dust map. ebv = float(resolve_ebv(dust_map(), coord)) params = {name: value[0] for name, value in tde.sed.sample_parameters(1, rng=12345).items()} print(f"E(B-V) at target: {ebv:.3f}") print("SED parameters:", {name: f"{value:.3g}" for name, value in params.items()}) .. rst-class:: sphx-glr-script-out .. code-block:: none E(B-V) at target: 0.007 SED parameters: {'amplitude': '2.36e+43 erg / s', 'sigma_rise': '3.58 d', 'tau_decline': '26 d', 'temperature': '1.44e+04 K'} .. GENERATED FROM PYTHON SOURCE LINES 45-55 Choosing the cadences ------------------------- Rubin's redder bands (``r``/``i``/``z``/``y``) get visited every 5 days and its bluer bands (``u``/``g``) every 10 days -- roughly the cadence split of the Rubin/LSST `wide-fast-deep baseline `_, all at Rubin's standard 30 s visit exposure time. UVEX joins on its own 10-day cadence for ``FUV``/``NUV``. Every band that shares an (instrument, cadence) pair also shares one time grid, so :meth:`~uvex_transients.models.core.base.SpectralModel.simulate_photometry` only needs to be called once per group. .. GENERATED FROM PYTHON SOURCE LINES 55-88 .. code-block:: Python RUBIN_CADENCES = { "u": 10 * u.day, "g": 10 * u.day, "r": 5 * u.day, "i": 5 * u.day, "z": 5 * u.day, "y": 5 * u.day, } RUBIN_EXPTIME = 30 * u.s UVEX_CADENCE = 20 * u.day UVEX_EXPTIME = 900 * u.s # Rubin/LSST's own systematic photometric calibration floor (Table 14 of the LSST # Science Requirements Document, LPM-17 -- the same `sigma_sys` `rubin_sim.phot_utils` # uses), in magnitudes. `simulate_photometry`'s own noise model is shot noise only # (source + sky Poisson, detector read/dark noise, via the standard CCD equation); it # has no notion of flat-fielding, PSF-fit, or zeropoint calibration systematics, which # is why bright-end Rubin points would otherwise come out implausibly precise. UVEX's # points are left as pure shot noise -- no comparably-established floor for it here. RUBIN_SIGMA_SYS = { "u": 0.0075, "g": 0.005, "r": 0.005, "i": 0.005, "z": 0.0075, "y": 0.0075, } rubin_band_groups: dict[u.Quantity, list[str]] = {} for band, cadence in RUBIN_CADENCES.items(): rubin_band_groups.setdefault(cadence, []).append(band) .. GENERATED FROM PYTHON SOURCE LINES 89-106 Simulating photometry -------------------------- As in the single-instrument case, there's no known real trigger time, so each instrument's background is chosen so that it doesn't need one: ``SkyBackground.medium()`` for Rubin (a fixed dark-sky brightness condition, no zodiacal light) and ``GalacticBackground()`` for UVEX (the Milky Way's diffuse UV glow only), leaving ``observer_location``/``obstime`` at their placeholder defaults. See :meth:`~uvex_transients.models.core.base.SpectralModel.simulate_photometry`'s own docstring for exactly when that placeholder default is (and isn't) safe. Each (instrument, cadence) group gets its own ``simulate_photometry`` call and the resulting tables are stacked together afterwards, tagged with an ``instrument`` column. Rubin's call also passes ``sys_err=RUBIN_SIGMA_SYS``, so its ``snr``/``flux_err``/``mag_err`` -- and the simulated ``flux``/``ab_mag`` draw itself -- reflect the combined shot-noise-plus-systematic uncertainty; UVEX's call doesn't, so its points stay pure shot noise. .. GENERATED FROM PYTHON SOURCE LINES 106-150 .. code-block:: Python tables = [] for cadence, bands in rubin_band_groups.items(): t_rubin = np.arange(0, tde.duration_limit.to_value(u.day), cadence.to_value(u.day)) * u.day phot_group = tde.sed.simulate_photometry( t_rubin, RUBIN_EXPTIME, rubin.detector, coord, bands=bands, background=SkyBackground.medium(), redshift=redshift, luminosity_distance=luminosity_distance, ebv=ebv, sys_err=RUBIN_SIGMA_SYS, rng=0, **params, ) phot_group["instrument"] = "Rubin" tables.append(phot_group) print(f"Rubin {'/'.join(bands)}: {len(t_rubin)} visits, one every {cadence}.") t_uvex = np.arange(0, tde.duration_limit.to_value(u.day), UVEX_CADENCE.to_value(u.day)) * u.day phot_uvex = tde.sed.simulate_photometry( t_uvex, UVEX_EXPTIME, uvex.detector, coord, background=GalacticBackground(), redshift=redshift, luminosity_distance=luminosity_distance, ebv=ebv, rng=0, **params, ) phot_uvex["instrument"] = "UVEX" print(f"UVEX FUV/NUV: {len(t_uvex)} visits, one every {UVEX_CADENCE}.") tables.append(phot_uvex) phot = vstack(tables) phot.sort(["t", "band"]) print(phot["t", "instrument", "band", "snr", "ab_mag"][:6]) .. rst-class:: sphx-glr-script-out .. code-block:: none Rubin u/g: 20 visits, one every 10.0 d. Rubin r/i/z/y: 40 visits, one every 5.0 d. UVEX FUV/NUV: 10 visits, one every 20.0 d. t instrument band snr ab_mag d --- ---------- ---- ---------------------- ------------------ 0.0 UVEX FUV 0.00013221681436506683 28.99481738028228 0.0 UVEX NUV 0.0006738824614370494 nan 0.0 Rubin g 0.0015027763939941836 nan 0.0 Rubin i 0.0006517172496378308 nan 0.0 Rubin r 0.0008886615886936758 29.203724142556172 0.0 Rubin u 0.0006981291295000424 28.824280282178023 .. GENERATED FROM PYTHON SOURCE LINES 151-159 The light curves ------------------- The noiseless theory curve (:meth:`~uvex_transients.models.core.base.SpectralModel.mag`, evaluated at each band's pivot wavelength) alongside the simulated visits from both instruments: detections above SNR=5 as points with error bars, fainter visits as downward-pointing upper limits -- the same plotting convention used at the end of :ref:`sphx_glr_auto_examples_simulating_plot_tde_end_to_end.py`. .. GENERATED FROM PYTHON SOURCE LINES 159-199 .. code-block:: Python SNR_THRESHOLD = 5.0 t_theory = np.linspace(0, tde.duration_limit.to_value(u.day), 300) * u.day band_detectors = {band: rubin.detector for band in RUBIN_CADENCES} | { "FUV": uvex.detector, "NUV": uvex.detector, } fig, ax = resolve_fig_axes(fig_size=(9, 5)) for band, detector in band_detectors.items(): nu = detector.bandpasses[band].pivot().to(u.Hz, equivalencies=u.spectral()) theory_mag = tde.sed.mag( nu, t_theory, redshift=redshift, luminosity_distance=luminosity_distance, log_attenuation=log_attenuation(nu, ebv), **params, ) plot_band_light_curve( ax, band, phot["t"], phot, t_theory=t_theory, theory_mag=theory_mag, snr_threshold=SNR_THRESHOLD, color=get_band_color(band), label=band, ) ax.invert_yaxis() ax.set_xlabel("Days since explosion") ax.set_ylabel("AB magnitude") ax.set_ylim([25, None]) ax.set_title(f"Rubin + UVEX ToO follow-up (z={redshift})") ax.legend(ncol=4, fontsize=8) fig.tight_layout() plt.show() .. image-sg:: /auto_examples/simulating/images/sphx_glr_plot_multiband_too_followup_001.png :alt: Rubin + UVEX ToO follow-up (z=0.2) :srcset: /auto_examples/simulating/images/sphx_glr_plot_multiband_too_followup_001.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 0.247 seconds) .. _sphx_glr_download_auto_examples_simulating_plot_multiband_too_followup.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: plot_multiband_too_followup.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: plot_multiband_too_followup.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: plot_multiband_too_followup.zip ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_