Simulating the detector noise

GWForge can generate a realisation of coloured Gaussian noise from a power spectral density, and it also generates strain data without any noise at all.

For example, here is a noise configuration file for XG detectors:

[IFOS]
detectors = ['CE20', 'CE40', 'ET']
sampling-frequency = 8192
noise = Gaussian

Which you can run as follows:

gwforge_noise --gps-start-time 1893024018 --gps-end-time 1893187858 --config-file xg.ini --output-directory data

This will generate roughly a day’s worth of data for an XG detector network at a sampling frequency of 8192Hz. The strain data will be a realisation of coloured Gaussian noise, and the noise power spectrum is read from the packaged curves in GWForge/ifo/noise_curves. The generated strain data is written as HDF5 to the output directory, one file per detector per chunk, named {IFO}/{IFO}-{GPS-START}-{DURATION}.h5, with channel names {IFO}:INJ.

You can inspect a file with gwpy:

from gwpy.timeseries import TimeSeries
data = TimeSeries.read('data/CE40/CE40-1893024018-4096.h5')
print(data.t0, data.dt, data.channel)

Alternatively, you can define the configuration file as follows:

[IFOS]
detectors = ['CE20', 'CE40', 'ET']
sampling-frequency = 8192
noise = Gaussian
gps-start-time = 1893024018
duration = 86400

And remove the gps option.

For zero noise, set noise = zero. Both noise types are produced by the same code path, so zero noise is available for every detector including ET.

Finally, you can supply your own noise curves:

[IFOS]
detectors = ['H1', 'L1', 'V1']
psd-dict = {'H1' : 'H1.txt', 'L1': 'L1.txt', 'V1':'V1.txt'}
sampling-frequency = 8192
noise = Gaussian
gps-start-time = 1893024018
duration = 86400

How the noise is generated

Noise comes from GWForge.ifo.reproducible_noise, which depends on nothing beyond numpy. It is a faithful reimplementation of pycbc.noise.reproduceable, and tests/test_reproducible_noise.py asserts the two agree to max|dx| / rms < 1e-10 — the residual is FFT round-off, so the outputs are numerically identical, not merely statistically equivalent.

The property this preserves is the one the workflow depends on: the realisation is keyed on (seed, absolute GPS time), not on the requested segment. White noise is drawn in fixed 1638400-sample blocks aligned to GPS zero, so two 4096 s chunks generated by independent Condor jobs — which never communicate — concatenate into exactly the stretch a single long call would have produced.

Warning

BLOCK_SAMPLES must never change. It is the quantum that makes the noise reproducible across disjoint spans; changing it silently re-rolls every dataset GWForge has ever produced.

Note

The white noise stitches across chunks exactly, but the coloured noise does not: colouring is a circular convolution over the whole padded span, so asking for 4096 s and asking for 8192 s give different (statistically equivalent) realisations over the overlap, differing by ~1e-3 of the RMS at the default filter length. PyCBC behaves the same way. This is why GWForge.utils.split_duration pins a fixed 4096 s chunk length — holding the chunking fixed is what makes a generated dataset reproducible.

Optional settings

Key

Meaning

noise

Gaussian or zero [Default: Gaussian].

ifo-seeds

Dict of per-detector seeds, e.g. {'CE40': 0, 'ET1': 1}. Defaults to an enumeration of the network.

filter-duration

Length in seconds of the colouring filter [Default: 128]. The noise is generated this much longer on each side and then cropped, so filter wrap-around never reaches the returned data.

noise-low-frequency-cutoff

Low-frequency cutoff in Hz. Defaults per detector to its own minimum_frequency.

Deprecated since version 0.1: noise-type (which used to select a bilby or pycbc backend) and fft-scheme are ignored. GWForge now generates all noise itself, and numpy’s FFT has no size limit to work around. Both keys log a warning and are otherwise harmless.