# 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: ```ini [IFOS] detectors = ['CE20', 'CE40', 'ET'] sampling-frequency = 8192 noise = Gaussian ``` Which you can run as follows: ```bash 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`: ```python 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: ```ini [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: ```ini [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 {mod}`GWForge.ifo.reproducible_noise`, which depends on nothing beyond numpy. It is a faithful reimplementation of [`pycbc.noise.reproduceable`](https://pycbc.org/pycbc/latest/html/_modules/pycbc/noise/reproduceable.html), 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} 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. ``` ## Overriding a detector's noise curve Detector tokens carry a sensitivity with them. `CE40`, `CE20` and `ET` come from GWForge's own `.ifo` files; everything else falls through to bilby, and bilby's choices are not always the ones a study wants. In particular, `H1` and `L1` carry `aLIGO_O4_high_asd.txt` and `V1` carries `AdV_psd.txt` — of the 2G detectors only `A1` (LIGO-India) is A+ out of the box. `gwforge_optimal_snr --psd-dict` replaces the curve without touching the geometry: ```bash gwforge_optimal_snr --injection-file bbh_population.h5 \ --output-file bbh_snr_aplus.h5 --ifos H1 L1 A1 \ --psd-dict "{'H1': 'GWForge/ifo/noise_curves/aplus.txt', 'L1': 'GWForge/ifo/noise_curves/aplus.txt', 'A1': 'GWForge/ifo/noise_curves/aplus.txt'}" \ --waveform-approximant IMRPhenomXPHM \ --minimum-frequency 20 --sampling-frequency 4096 ``` `gwforge_fisher` takes the same thing as `[IFOS] psd-dict` in its ini. Both route to {class}`GWForge.ifo.detectors.Network`, which decides ASD versus PSD from the magnitude of the array, so either file type works and no flag says which. Detectors left out of the dict keep their shipped curve. ```{important} `--psd-dict` sets the **noise curve only**. The integration band still comes from the interferometer's own `minimum_frequency` and `maximum_frequency`, which `--minimum-frequency` does not change — that flag sets where the *waveform* starts. So `--minimum-frequency 5` against a detector whose `.ifo` says `minimum_frequency = 6` still yields an SNR integrated from 6 Hz. ``` A quick way to confirm an override took: run a handful of events with and without it. A no-op leaves the SNRs bit-identical, and swapping A+ for O4-high moves H1 and L1 by a factor of about two.