Simulating data with pygwb.simulator

In this tutorial, we show how the pygwb.simulator module can be used to simulate a gravitational-wave background. We consider both the case where a power-law spectrum is injected, and where individual compact binary coalescences (CBCs) are injected. For more information about the module, we refer the reader to the pygwb.simulator documentation page.

1. Simulating a stochastic gravitational-wave background

1.1 Injecting a power spectrum in random LIGO noise

For concreteness, we consider a broken power-law signal power spectral density (PSD) to be injected in random LIGO Gaussian noise. The simulator module takes a gwpy.FrequencySeries as input for the signal PSD to be injected. We start by building a custom input signal by defining an IntensityGW function, which outputs the desired signal PSD in the form of a gwpy.FrequencySeries.

frequencies_x = np.linspace(0, 1000, 10000)

alpha1 = 6
alpha2 = 0
fref = 10
omegaRef = 5.e-5

def IntensityGW(freqs, omegaRef, alpha1, fref, alpha2 = 2/3):
    ''' GW Intensity function from broken power law in OmegaGW

    Parameters
    ----------

    freqs: np.array
        frequency array
    fref:
        reference frequency
    omegaRef:
        Value of OmegaGW at reference frequency
    alpha1:
        first spectral index
    alpha2:
        second spectral index

    Return
    ------
    FrequencySeries
    '''
    from pygwb.constants import H0
    H_theor = (3 * H0.si.value ** 2) / (10 * np.pi ** 2)

    fknee = fref

    power = np.zeros_like(freqs)

    power[freqs<fknee] = H_theor * omegaRef * (freqs[freqs<fknee]) ** (alpha1 -3) * fref**(-alpha1)
    power[freqs>fknee] = H_theor * omegaRef * (freqs[freqs>fknee]) ** (alpha2 - 3) * fref**(-alpha2)
    power[freqs==fknee] = H_theor * omegaRef * (fknee) ** (alpha2 -3) * fref**(-alpha2)

    power[0] = power[1]

    return gwpy.frequencyseries.FrequencySeries(power, frequencies=freqs)

Intensity_GW_inject = IntensityGW(frequencies_x, omegaRef = omegaRef, alpha1 = alpha1, fref = fref)

Note that the above signal PSD was chosen for illustrative purposes. However, in practice, any signal PSD can be chosen to be injected, and one does not need to restrict themselves to a broken power-law.

See also

More information about gwpy.frequencyseries.FrequencySeries can be found here.

One also needs to specify the parameters that will serve as input to the simulator. Concretely, we specify the duration of each simulated segment, the number of segments, and the sampling frequency.

duration = 60 # duration of each segment of data (s)
N_segs = 10  # number of data segments to generate
sampling_frequency = 1024 # Hz

Tip

Not sure about what the above parameters do? Make sure to check out the documentation of the simulator module.

The detectors for which data with the above signal PSD need to be simulated, have to be passed to the simulator module. By relying on the detector module, we instantiate various detectors below. In addition, we note that these detectors are Interferometer objects, but are based on bilby detectors, which have default noise PSDs saved in them, in the power_spectral_density attribute of the bilby detector. Below, we load in this noise PSD and make sure the duration and sampling frequency of the detector are set to the desired value of these parameters.

H1 = Interferometer.get_empty_interferometer("H1") #LIGO Hanford detector
L1 = Interferometer.get_empty_interferometer("L1") #LIGO Livingston detector

ifo_list = [H1, L1]

for ifo in ifo_list:
    ifo.duration = duration
    ifo.sampling_frequency = sampling_frequency
    ifo.power_spectral_density = bilby.gw.detector.PowerSpectralDensity(ifo.frequency_array, np.nan_to_num(ifo.power_spectral_density_array, posinf=1.e-41))

 net_HL = Network('HL', ifo_list)

See also

Additional information about the Interferometer object can be found here. For more information, we also refer the user to the bilby documentation.

We are now ready to simulate the data, consisting of a signal and Gaussian noise, colored by the noise PSD saved in each of the detectors. We rely on the network module to simulate the data by calling the set_interferometer_data_from_simulator() method (which uses the simulator module). More information on the method can be found here.

net_HL.set_interferometer_data_from_simulator(N_segments=N_segs, GWB_intensity=Intensity_GW_inject, sampling_frequency=sampling_frequency)

Note

One may save the data by calling pygwb.network.save_interferometer_data_to_file() (see here) and specifying the file format as an argument. This wraps the gwpy.TimeSeries.write() method (more details can be found here).

1.2 Injecting a power spectrum in real data

Alternatively, one could decide to inject a SGWB in real detector data. To illustrate this functionality, we inject the same signal as above in real LIGO data. The detectors are instantiated through the parameters module, which allows to load the parameters, including the GPS times used to retrieve real data.

params = Parameters()
params.update_from_file(path="../test/test_data/parameters_baseline_test.ini")
params.t0=1247644204
params.tf=1247645100
params.segment_duration=128

Tip

Not sure how the parameters module works anymore? Make sure to check out the documentation.

We now create the two Interferometer objects that will be used for the data simulation (LIGO Hanford (H1) and LIGO Livingstn (L1) for this concrete example).

H1 = Interferometer.from_parameters(params.interferometer_list[0], params)
L1 = Interferometer.from_parameters(params.interferometer_list[1], params)

ifo_list = [H1, L1]

See also

Additional informational information about the Interferometer object can be found here.

Note that the interferometers above contain the desired data in which we want to inject the signal. We now make sure the duration and sampling frequency of the detector are set to the desired value of these parameters, as specified in the parameters object defined at the start of this example. The strain data in the interferometer is also set to the real data considered in this example.

for ifo in ifo_list:
    ifo.sampling_frequency = params.new_sample_rate
    ifo.set_strain_data_from_gwpy_timeseries(gwpy.timeseries.TimeSeries(data=ifo.timeseries.value, times=ifo.timeseries.times))
    ifo.duration=params.segment_duration

To inject a signal in real data, we rely on the network module, for which an object is instantiated below. To simulate the data, one calls set_interferometer_data_from_simulator() method (which uses the simulator module). More information on the method can be found here. Note that the inject_into_data_flag is set to True, indicating the data will be injected in real data, and that additional Gaussian colored noise therefore does not need to be simulated, nor injected on top of the signal.

HL_baseline = Baseline.from_parameters(H1, L1, params)
net_HL = Network.from_baselines("HL_network", [HL_baseline])

net_HL.set_interferometer_data_from_simulator(N_segments=7, GWB_intensity=Intensity_GW_inject, sampling_frequency=H1.sampling_frequency, inject_into_data_flag=True)

Note

One may save the data by calling pygwb.network.save_interferometer_data_to_file() (see here) and specifying the file format as an argument. This wraps the gwpy.TimeSeries.write() method (more details can be found here).

2. Injecting individual CBC events

2.1 Initialising empty interferometers and parameters for simulation

We start by specifying the parameters that will serve as input to the simulator. Concretely, we specify the duration of each simulated segment, the number of segments, and the sampling frequency.

duration = 64 # duration of each segment of data (s)
N_segs = 5  # number of data segments to generate
sampling_frequency = 1024 # Hz

Tip

Not sure about what the above parameters do? Make sure to check out the documentation of the simulator module.

The detectors for which data need to be simulated, have to be passed to the simulator module. By relying on the detector module, we instantiate various detectors below. We decide to use H1 and L1 as an example. However, note that the data can be simulated for an arbitrary amount of detectors. One would simply add more detectors to the ifo_list below.

ifo_H1 = Interferometer.get_empty_interferometer('H1')
ifo_L1 = Interferometer.get_empty_interferometer('L1')

ifo_list = [ifo_H1, ifo_L1]

See also

Additional informational information about the Interferometer object can be found here. For more information, we also refer the reader to the bilby documentation.

The above detectors are Interferometer objects, but are based on bilby detectors, which have default noise PSDs saved in them, in the power_spectral_density attribute of the bilby detector. Below, we load in this noise PSD and make sure the duration and sampling frequency of the detector are set to the desired value of these parameters.

for ifo in ifo_list:
    ifo.duration = duration
    ifo.sampling_frequency = sampling_frequency
    ifo.power_spectral_density = bilby.gw.detector.PowerSpectralDensity(ifo.frequency_array, np.nan_to_num(ifo.power_spectral_density_array, posinf=1.e-41))
net_HL = Network('HL', ifo_list)

2.2 Specifying the CBC population

Before being able to simulate CBCs, we need to specify which population the CBC events are drawn from. This is done by using bilby priors. This allows the user to specify the distributions of the various parameters that come into play in CBC waveforms. A few examples are given below.

priors = bilby.gw.prior.BBHPriorDict(aligned_spin=True)
priors['chirp_mass'] = bilby.core.prior.Uniform(2, 30, name="chirp_mass")
priors['mass_ratio'] = 1.0
priors['chi_1'] = 0
priors['chi_2'] = 0
priors['luminosity_distance'] = bilby.core.prior.PowerLaw(alpha=2, name='luminosity_distance',
                                                      minimum=10, maximum=100,
                                                      unit='Mpc')
priors["geocent_time"] = bilby.core.prior.Uniform(0, duration*N_segs, name="geocent_time")

# create 20 injections
injections = priors.sample(20)

See also

For additional information on bilby prior dictionaries, we refer the user to the documentation.

The output of the cell above is a dictionary containing the injections, which will serve as input for the simulator. It can be very useful to save these injections to file for later use. This is done by executing the following lines of code:

import json

with open("injections.json", "w") as file:
    json.dump(
        injections, file, indent=2, cls=bilby.core.result.BilbyJsonEncoder
    )

2.3 Simulating CBCs and Gaussian noise

We are now ready to simulate the data, consisting of CBCs and Gaussian noise, colored by the noise PSD saved in each of the detectors. We rely on the pygwb.network module to simulate the data by calling the set_interferometer_data_from_simulator() method (which uses the pygwb.simulator module). More information on the method can be found here.

net_HL.set_interferometer_data_from_simulator(N_segs, CBC_dict=injections, sampling_frequency = sampling_frequency)

Note

One may save the data by calling pygwb.network.save_interferometer_data_to_file() (see here) and specifying the file format as an argument. This wraps the gwpy.TimeSeries.write() method (more details can be found here).