Time Varying EffectModels

The impact of an EffectModel can vary with time and wavelength (or filter). In this notebook, we show how to construct a basic time varying effect model.

In this notebook we model a Gaussian activation function, such as we might see if the contribution of an object to the observed flux followed a Gaussian curve over time. For example we might have a high proper motion star whose contribution to the current field of view varies with time and PSF as it moves into and then out of the field of view. The percentage of its flux is determined by the Gaussian smearing of the PSF.

[1]:
import numpy as np
import matplotlib.pyplot as plt

from lightcurvelynx.effects.effect_model import EffectModel
from lightcurvelynx.models.basic_models import ConstantSEDModel
/home/docs/checkouts/readthedocs.org/user_builds/lightcurvelynx/envs/stable/lib/python3.12/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
  from .autonotebook import tqdm as notebook_tqdm

We start be defining our new effect model. Below we only show the implementation for apply() which applies the effects to the SED. The code for apply_bandflux() would be almost identical. Our new model inherits from the base effect model.

[2]:
class GaussianActivation(EffectModel):
    """An effect that applies a Gaussian multiplier to the flux density.
    This is primarily included as a demonstration of how to implement effects
    that vary with time, but could be used to model a fast moving star that
    moves through the field of view.

    flux = flux * exp(-0.5 * ((time - t0) / decay) ** 2)

    Attributes
    ----------
    t0 : parameter
        The peak time of the effect (in days).
    decay : parameter
        The decay time of the effect (in days).
    rest_frame : bool
        Whether the effect is applied in the rest frame of the observation (True)
        or in the observed frame (False).
    """

    def __init__(self, t0, decay, rest_frame=True, **kwargs):
        super().__init__(**kwargs)

        # Define the effect's parameters. Note that these do not need to match
        # the argument names. They should be chosen so that they do not overlap
        # parameters within the model node (e.g. we can't use t0).
        self.add_effect_parameter("gaussian_activation_t0", t0)
        self.add_effect_parameter("gaussian_activation_decay", decay)

        # Set the rest_frame parameter.
        self.rest_frame = rest_frame

    def apply(
        self,
        flux_density,
        times=None,
        wavelengths=None,
        gaussian_activation_t0=None,
        gaussian_activation_decay=None,
        **kwargs,
    ):
        """Apply the effect to observations (flux_density values).

        Parameters
        ----------
        flux_density : numpy.ndarray
            A length T X N matrix of flux density values (in nJy).
        times : numpy.ndarray, optional
            A length T array of times (in MJD). Not used for this effect.
        wavelengths : numpy.ndarray, optional
            A length N array of wavelengths (in angstroms). Not used for this effect.
        gaussian_activation_t0 : float, optional
            The peak time of the effect (in days). Raises an error if None is provided.
        gaussian_activation_decay : float, optional
            The decay time of the effect (in days). Raises an error if None is provided.
        **kwargs : `dict`, optional
           Any additional keyword arguments, including any additional
           parameters needed to apply the effect.

        Returns
        -------
        flux_density : numpy.ndarray
            A length T x N matrix of flux densities after the effect is applied (in nJy).
        """
        # Basic error checking to make sure we got values for the parameters we need.
        if gaussian_activation_t0 is None or gaussian_activation_decay is None:
            raise ValueError("gaussian_activation_t0 and gaussian_activation_decay must be provided")
        if times is None:
            raise ValueError("times must be provided")

        # Compute the scale at each time and apply it.
        scale = np.exp(-0.5 * ((times - gaussian_activation_t0) / gaussian_activation_decay) ** 2)
        return flux_density * scale[:, np.newaxis]

Now that we have an effect model, we can create a model object and apply the effect.

[3]:
model = ConstantSEDModel(brightness=100.0)
model.add_effect(GaussianActivation(t0=5.0, decay=2.0))

We then run a simulation on this object and plot the flux density at the first wavelength over our time range.

[4]:
times = np.arange(0.0, 20.5, 0.5)
wavelengths = np.array([1000.0, 2000.0, 3000.0, 4000.0])
flux_density = model.evaluate_sed(times=times, wavelengths=wavelengths)

# Plot the first wavelength's flux density over time
plt.plot(times, flux_density[:, 0])
plt.xlabel("Time")
plt.ylabel("Flux")
plt.show()
../_images/notebooks_time_varying_effects_7_0.png

We can make the simulation more realistic by defining a compound object and applying this activation to only one of the sources.

[5]:
from lightcurvelynx.models.multi_object_model import AdditiveMultiObjectModel

model1 = ConstantSEDModel(brightness=50.0)

model2 = ConstantSEDModel(brightness=100.0)
model2.add_effect(GaussianActivation(t0=9.0, decay=1.0))

combined_model = AdditiveMultiObjectModel([model1, model2])

# Evaluate the combined model at different times.
wavelengths = np.array([4000.0])
fluxes1 = model1.evaluate_sed(times, wavelengths)
fluxes2 = model2.evaluate_sed(times, wavelengths)
fluxes_all = combined_model.evaluate_sed(times, wavelengths)

plt.plot(times, fluxes_all, label="Combined Model")
plt.plot(times, fluxes1, label="Model1")
plt.plot(times, fluxes2, label="Model2")
plt.xlabel("Time")
plt.ylabel("Flux")
plt.legend()
plt.show()
../_images/notebooks_time_varying_effects_9_0.png