[1]:
%load_ext autoreload
%autoreload 2
%matplotlib inline

import copy
import glob
import numpy as np

import matplotlib as mpl
import matplotlib.pyplot as plt

params = {
    'font.size': 7,
    'xtick.major.size': 0,
    'xtick.minor.size': 0,
    'xtick.major.width': 0,
    'xtick.minor.width': 0,
    'ytick.major.size': 0,
    'ytick.minor.size': 0,
    'ytick.major.width': 0,
    'ytick.minor.width': 0,
    'figure.figsize': (6, 4),
    "axes.grid" : True,
    "grid.color": "grey",
    "grid.linewidth": 0.3,
    "grid.linestyle": ":",
    "axes.grid.axis": "y",
    "axes.grid.which": "both",
    "axes.axisbelow": False,
    'axes.labelsize': 8,
    'xtick.labelsize': 8,
    'ytick.labelsize': 8,
    'legend.fontsize': 8,
    'legend.title_fontsize': 8,
    'figure.dpi': 300,  # the left side of the subplots of the figure
    'figure.subplot.left': 0.195,  # the left side of the subplots of the figure
    'figure.subplot.right': 0.97,   # the right side of the subplots of the figure
    'figure.subplot.bottom': 0.145,   # the bottom of the subplots of the figure
    'figure.subplot.top': 0.97,   # the top of the subplots of the figure
    'figure.subplot.wspace': 0.2,    # the amount of width reserved for space between subplots,
                                   # expressed as a fraction of the average axis width
    'figure.subplot.hspace': 0.2,    # the amount of height reserved for space between subplots,
                               # expressed as a fraction of the average axis height
}
mpl.rcParams.update(params)
[2]:
def find_inverse(real, imaginary, N):

    # Form complex numbers corresponding to each frequency
    f = [complex(r, i) for r, i in zip(real, imaginary)]

    f = np.hstack([0, f])
    # Obtain time series
    return np.fft.irfft(f, n=N)


def scale_lc(lc, mean, rms):

    lc_mean = np.mean(lc)
    lc_std = np.std(lc)

    return ((lc - lc_mean) / lc_std * rms + 1) * mean


def timmerkoenig(pds_shape, mean, rms):
    pds_size = pds_shape.size

    real = np.random.normal(size=pds_size) * np.sqrt(0.5 * pds_shape)
    imaginary = np.random.normal(size=pds_size) * np.sqrt(0.5 * pds_shape)
    imaginary[-1] = 0

    flux = find_inverse(real, imaginary, N=2 * pds_size)

    rescaled_flux = scale_lc(flux, mean, rms)

    return rescaled_flux

Let us start with a standard light curve simulation with the Timmer & Koenig method:

[3]:
from astropy.modeling import models

pds_model = \
    models.PowerLaw1D(x_0=1, alpha=1, amplitude=1)

nyq = 100.
freq = np.linspace(0, nyq, 1000)[1:]

pds_shape = pds_model(freq)
mean = 10
rms = 0.3

dt = 0.5 / nyq

flux = timmerkoenig(pds_shape, mean, rms)
times = dt * np.arange(flux.size)

plt.plot(times, flux)
[3]:
[<matplotlib.lines.Line2D at 0x7fd1916a8e50>]
../../../_images/notebooks_Simulator_Concepts_Simulate_Event_Lists_With_Inverse_CDF_3_1.png

Simulating event times with the inverse CDF methodΒΆ

Given a positive-definite light curve (generated, e.g., with the method by Timmer & Koenig), we treat it as a probability distribution: we calculate the cumulative distribution function by calculating its cumulative sum and normalizing to 1. Then, we generate random numbers uniformly distributed between 0 and 1 (horizontal lines) and take the event times at the corresponding values of the CDF (vertical lines).

[4]:
from scipy.interpolate import interp1d

def cdf_from_lc(lc, dt):
    cdf = np.cumsum(lc)
    cdf = np.concatenate([[0], cdf])
    cdf /= cdf.max()
    return cdf


# cdf_times = np.concatenate([[0], dt / 2 + time])
cdf_values = cdf_from_lc(flux, dt)
cdf_times = np.arange(cdf_values.size) * dt

cdf_inverse = interp1d(cdf_values, cdf_times)

plt.plot(times, flux / flux.max(), color="grey", label="Light curve")
plt.plot(cdf_times, cdf_values, color="k", label="CDF")

for prob_val in np.linspace(0, 1, 100):
    time = cdf_inverse(prob_val)
    plt.plot([0, time], [prob_val, prob_val], color="r", lw=0.3)
    plt.plot([time, time], [0, prob_val], color="r", lw=0.3)

plt.xlabel("Time")
plt.ylabel("Probability")

plt.ylim([0, 1])
plt.xlim([0, 10])
plt.legend(loc="lower right");
plt.tight_layout()
plt.savefig("CDF_lc.jpg")
../../../_images/notebooks_Simulator_Concepts_Simulate_Event_Lists_With_Inverse_CDF_5_0.png

The same method can be used, in principle, to simulate variates from any probability distribution. The only requirement is that the input distribution is positive definite. Stingray implements this method in stingray.simulator.base:

[5]:
from stingray.simulator.base import simulate_with_inverse_cdf
event_times = simulate_with_inverse_cdf(flux, 10)
[6]:
event_times
[6]:
array([0.3809308 , 0.10856514, 0.71888075, 0.54479831, 0.87783205,
       0.45405823, 0.66623686, 0.62832368, 0.72111516, 0.25882679])
[ ]: