gwsim 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gwsim/__init__.py +11 -0
- gwsim/__main__.py +8 -0
- gwsim/cli/__init__.py +0 -0
- gwsim/cli/config.py +88 -0
- gwsim/cli/default_config.py +56 -0
- gwsim/cli/main.py +101 -0
- gwsim/cli/merge.py +150 -0
- gwsim/cli/repository/__init__.py +0 -0
- gwsim/cli/repository/create.py +91 -0
- gwsim/cli/repository/delete.py +51 -0
- gwsim/cli/repository/download.py +54 -0
- gwsim/cli/repository/list_depositions.py +63 -0
- gwsim/cli/repository/main.py +38 -0
- gwsim/cli/repository/metadata/__init__.py +0 -0
- gwsim/cli/repository/metadata/main.py +24 -0
- gwsim/cli/repository/metadata/update.py +58 -0
- gwsim/cli/repository/publish.py +52 -0
- gwsim/cli/repository/upload.py +74 -0
- gwsim/cli/repository/utils.py +47 -0
- gwsim/cli/repository/verify.py +61 -0
- gwsim/cli/simulate.py +220 -0
- gwsim/cli/simulate_utils.py +596 -0
- gwsim/cli/utils/__init__.py +85 -0
- gwsim/cli/utils/checkpoint.py +178 -0
- gwsim/cli/utils/config.py +347 -0
- gwsim/cli/utils/hash.py +23 -0
- gwsim/cli/utils/retry.py +62 -0
- gwsim/cli/utils/simulation_plan.py +439 -0
- gwsim/cli/utils/template.py +56 -0
- gwsim/cli/utils/utils.py +149 -0
- gwsim/cli/validate.py +255 -0
- gwsim/data/__init__.py +8 -0
- gwsim/data/serialize/__init__.py +9 -0
- gwsim/data/serialize/decoder.py +59 -0
- gwsim/data/serialize/encoder.py +44 -0
- gwsim/data/serialize/serializable.py +33 -0
- gwsim/data/time_series/__init__.py +3 -0
- gwsim/data/time_series/inject.py +104 -0
- gwsim/data/time_series/time_series.py +355 -0
- gwsim/data/time_series/time_series_list.py +182 -0
- gwsim/detector/__init__.py +8 -0
- gwsim/detector/base.py +156 -0
- gwsim/detector/detectors/E1_2L_Aligned_Sardinia.interferometer +22 -0
- gwsim/detector/detectors/E1_2L_Misaligned_Sardinia.interferometer +22 -0
- gwsim/detector/detectors/E1_Triangle_EMR.interferometer +19 -0
- gwsim/detector/detectors/E1_Triangle_Sardinia.interferometer +19 -0
- gwsim/detector/detectors/E2_2L_Aligned_EMR.interferometer +22 -0
- gwsim/detector/detectors/E2_2L_Misaligned_EMR.interferometer +22 -0
- gwsim/detector/detectors/E2_Triangle_EMR.interferometer +19 -0
- gwsim/detector/detectors/E2_Triangle_Sardinia.interferometer +19 -0
- gwsim/detector/detectors/E3_Triangle_EMR.interferometer +19 -0
- gwsim/detector/detectors/E3_Triangle_Sardinia.interferometer +19 -0
- gwsim/detector/noise_curves/ET_10_HF_psd.txt +3000 -0
- gwsim/detector/noise_curves/ET_10_full_cryo_psd.txt +3000 -0
- gwsim/detector/noise_curves/ET_15_HF_psd.txt +3000 -0
- gwsim/detector/noise_curves/ET_15_full_cryo_psd.txt +3000 -0
- gwsim/detector/noise_curves/ET_20_HF_psd.txt +3000 -0
- gwsim/detector/noise_curves/ET_20_full_cryo_psd.txt +3000 -0
- gwsim/detector/noise_curves/ET_D_psd.txt +3000 -0
- gwsim/detector/utils.py +90 -0
- gwsim/glitch/__init__.py +7 -0
- gwsim/glitch/base.py +69 -0
- gwsim/mixin/__init__.py +8 -0
- gwsim/mixin/detector.py +203 -0
- gwsim/mixin/gwf.py +192 -0
- gwsim/mixin/population_reader.py +175 -0
- gwsim/mixin/randomness.py +107 -0
- gwsim/mixin/time_series.py +295 -0
- gwsim/mixin/waveform.py +47 -0
- gwsim/noise/__init__.py +19 -0
- gwsim/noise/base.py +134 -0
- gwsim/noise/bilby_stationary_gaussian.py +117 -0
- gwsim/noise/colored_noise.py +275 -0
- gwsim/noise/correlated_noise.py +257 -0
- gwsim/noise/pycbc_stationary_gaussian.py +112 -0
- gwsim/noise/stationary_gaussian.py +44 -0
- gwsim/noise/white_noise.py +51 -0
- gwsim/repository/__init__.py +0 -0
- gwsim/repository/zenodo.py +269 -0
- gwsim/signal/__init__.py +11 -0
- gwsim/signal/base.py +137 -0
- gwsim/signal/cbc.py +61 -0
- gwsim/simulator/__init__.py +7 -0
- gwsim/simulator/base.py +315 -0
- gwsim/simulator/state.py +85 -0
- gwsim/utils/__init__.py +11 -0
- gwsim/utils/datetime_parser.py +44 -0
- gwsim/utils/et_2l_geometry.py +165 -0
- gwsim/utils/io.py +167 -0
- gwsim/utils/log.py +145 -0
- gwsim/utils/population.py +48 -0
- gwsim/utils/random.py +69 -0
- gwsim/utils/retry.py +75 -0
- gwsim/utils/triangular_et_geometry.py +164 -0
- gwsim/version.py +7 -0
- gwsim/waveform/__init__.py +7 -0
- gwsim/waveform/factory.py +83 -0
- gwsim/waveform/pycbc_wrapper.py +37 -0
- gwsim-0.1.0.dist-info/METADATA +157 -0
- gwsim-0.1.0.dist-info/RECORD +103 -0
- gwsim-0.1.0.dist-info/WHEEL +4 -0
- gwsim-0.1.0.dist-info/entry_points.txt +2 -0
- gwsim-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# pylint: disable=duplicate-code
|
|
2
|
+
"""Correlated noise simulator for multiple gravitational wave detectors."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
from scipy.interpolate import interp1d
|
|
11
|
+
from scipy.linalg import cholesky
|
|
12
|
+
from scipy.sparse import block_diag, coo_matrix
|
|
13
|
+
|
|
14
|
+
from gwsim.data.time_series.time_series import TimeSeries
|
|
15
|
+
from gwsim.data.time_series.time_series_list import TimeSeriesList
|
|
16
|
+
from gwsim.noise.base import NoiseSimulator
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger("gwsim")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class CorrelatedNoiseSimulator(NoiseSimulator): # pylint: disable=too-many-instance-attributes
|
|
22
|
+
"""Correlated noise simulator for multiple gravitational wave detectors.
|
|
23
|
+
|
|
24
|
+
This class generates noise time series with specified power spectral density (PSD)
|
|
25
|
+
and cross-spectral density (CSD) for multiple detectors. The correlations between
|
|
26
|
+
detectors are modeled using Cholesky decomposition of the spectral matrix.
|
|
27
|
+
|
|
28
|
+
The noise generation uses an overlap-add method with windowing to produce
|
|
29
|
+
smooth, continuous time series across segment boundaries.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__( # pylint: disable=too-many-arguments,too-many-positional-arguments,duplicate-code
|
|
33
|
+
self,
|
|
34
|
+
psd_file: str | Path,
|
|
35
|
+
csd_file: str | Path,
|
|
36
|
+
detectors: list[str],
|
|
37
|
+
sampling_frequency: float = 4096,
|
|
38
|
+
duration: float = 4,
|
|
39
|
+
start_time: float = 0,
|
|
40
|
+
max_samples: int | None = None,
|
|
41
|
+
seed: int | None = None,
|
|
42
|
+
low_frequency_cutoff: float = 2.0,
|
|
43
|
+
high_frequency_cutoff: float | None = None,
|
|
44
|
+
**kwargs,
|
|
45
|
+
):
|
|
46
|
+
"""Initialize the correlated noise simulator.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
psd_file: Path to file containing Power Spectral Density array with shape (N, 2),
|
|
50
|
+
where the first column is frequency (Hz) and the second is PSD values.
|
|
51
|
+
csd_file: Path to file containing Cross Spectral Density array with shape (N, 2),
|
|
52
|
+
where the first column is frequency (Hz) and the second is complex CSD values.
|
|
53
|
+
detectors: List of detector names (e.g., ['E1', 'E2', 'E3']).
|
|
54
|
+
sampling_frequency: Sampling frequency in Hz. Default is 4096.
|
|
55
|
+
duration: Duration of each noise segment in seconds. Default is 4.
|
|
56
|
+
start_time: GPS start time for the time series. Default is 0.
|
|
57
|
+
max_samples: Maximum number of samples to generate. None means infinite.
|
|
58
|
+
seed: Seed for random number generation. If None, RNG is not initialized.
|
|
59
|
+
low_frequency_cutoff: Lower frequency cutoff in Hz. Default is 2.0.
|
|
60
|
+
high_frequency_cutoff: Upper frequency cutoff in Hz. Default is Nyquist frequency.
|
|
61
|
+
**kwargs: Additional arguments passed to parent classes.
|
|
62
|
+
|
|
63
|
+
Raises:
|
|
64
|
+
ValueError: If detectors list is empty.
|
|
65
|
+
"""
|
|
66
|
+
if not detectors or len(detectors) == 0:
|
|
67
|
+
raise ValueError("detectors must contain at least one detector.")
|
|
68
|
+
|
|
69
|
+
super().__init__( # pylint: disable=duplicate-code
|
|
70
|
+
sampling_frequency=sampling_frequency,
|
|
71
|
+
duration=duration,
|
|
72
|
+
start_time=start_time,
|
|
73
|
+
max_samples=max_samples,
|
|
74
|
+
seed=seed,
|
|
75
|
+
detectors=detectors,
|
|
76
|
+
**kwargs,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
self.psd_file = psd_file
|
|
80
|
+
self.csd_file = csd_file
|
|
81
|
+
self.low_frequency_cutoff = low_frequency_cutoff
|
|
82
|
+
self.high_frequency_cutoff = (
|
|
83
|
+
high_frequency_cutoff
|
|
84
|
+
if (high_frequency_cutoff is not None and high_frequency_cutoff <= sampling_frequency / 2)
|
|
85
|
+
else sampling_frequency / 2
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# Initialize noise generation properties
|
|
89
|
+
self._n_det = len(detectors)
|
|
90
|
+
self._initialize_window_properties()
|
|
91
|
+
self._initialize_frequency_properties()
|
|
92
|
+
self._initialize_psd_csd()
|
|
93
|
+
self._spectral_matrix = self._compute_spectral_matrix_cholesky()
|
|
94
|
+
|
|
95
|
+
def _initialize_window_properties(self) -> None:
|
|
96
|
+
"""Initialize window properties for overlap-add noise generation."""
|
|
97
|
+
self._f_window = self.low_frequency_cutoff / 100
|
|
98
|
+
self._t_window = 1 / self._f_window
|
|
99
|
+
self._t_overlap = self._t_window / 2.0
|
|
100
|
+
self._n_overlap = int(self._t_overlap * self.sampling_frequency.value)
|
|
101
|
+
|
|
102
|
+
# Create overlap windows for smooth transitions
|
|
103
|
+
t_overlap_array = np.linspace(0, self._t_overlap, self._n_overlap)
|
|
104
|
+
self._w0 = 0.5 + np.cos(2 * np.pi * self._f_window * t_overlap_array) / 2
|
|
105
|
+
self._w1 = 0.5 + np.sin(2 * np.pi * self._f_window * t_overlap_array - np.pi / 2) / 2
|
|
106
|
+
|
|
107
|
+
def _initialize_frequency_properties(self) -> None:
|
|
108
|
+
"""Initialize frequency and time properties for noise generation."""
|
|
109
|
+
self._t_segment = self._t_window * 3
|
|
110
|
+
self._df = 1.0 / self._t_segment
|
|
111
|
+
self._dt = 1.0 / self.sampling_frequency.value
|
|
112
|
+
self._n_samples = int(self._t_segment * self.sampling_frequency.value)
|
|
113
|
+
self._k_min = int(self.low_frequency_cutoff / self._df)
|
|
114
|
+
self._k_max = int(self.high_frequency_cutoff / self._df) + 1
|
|
115
|
+
self._frequency = np.arange(0.0, self._n_samples / 2.0 + 1) * self._df
|
|
116
|
+
self._n_freq = len(self._frequency[self._k_min : self._k_max])
|
|
117
|
+
|
|
118
|
+
def _load_spectral_data(self, file_path: str | Path) -> np.ndarray: # pylint: disable=duplicate-code
|
|
119
|
+
"""Load spectral data from file.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
file_path: Path to file containing spectral data.
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
Loaded array.
|
|
126
|
+
|
|
127
|
+
Raises:
|
|
128
|
+
ValueError: If file format is not supported.
|
|
129
|
+
TypeError: If file_path is not a string or Path.
|
|
130
|
+
"""
|
|
131
|
+
if isinstance(file_path, (str, Path)):
|
|
132
|
+
path = Path(file_path)
|
|
133
|
+
if path.suffix == ".npy":
|
|
134
|
+
return np.load(path)
|
|
135
|
+
if path.suffix == ".txt":
|
|
136
|
+
return np.loadtxt(path)
|
|
137
|
+
if path.suffix == ".csv":
|
|
138
|
+
return np.loadtxt(path, delimiter=",")
|
|
139
|
+
raise ValueError(f"Unsupported file format: {path.suffix}. Use .npy, .txt, or .csv.")
|
|
140
|
+
raise TypeError("file_path must be a string or Path.")
|
|
141
|
+
|
|
142
|
+
def _initialize_psd_csd(self) -> None: # pylint: disable=duplicate-code
|
|
143
|
+
"""Initialize PSD and CSD interpolations for the frequency range.
|
|
144
|
+
|
|
145
|
+
Raises:
|
|
146
|
+
ValueError: If PSD or CSD arrays don't have shape (N, 2).
|
|
147
|
+
"""
|
|
148
|
+
psd_data = self._load_spectral_data(self.psd_file)
|
|
149
|
+
csd_data = self._load_spectral_data(self.csd_file)
|
|
150
|
+
|
|
151
|
+
if psd_data.shape[1] != 2 or csd_data.shape[1] != 2:
|
|
152
|
+
raise ValueError("PSD and CSD files must have shape (N, 2).")
|
|
153
|
+
|
|
154
|
+
# Interpolate to the relevant frequencies
|
|
155
|
+
freqs = self._frequency[self._k_min : self._k_max]
|
|
156
|
+
self._psd = interp1d(psd_data[:, 0], psd_data[:, 1], bounds_error=False, fill_value="extrapolate")(freqs)
|
|
157
|
+
|
|
158
|
+
csd_real = interp1d(csd_data[:, 0], csd_data[:, 1].real, bounds_error=False, fill_value="extrapolate")(freqs)
|
|
159
|
+
csd_imag = interp1d(csd_data[:, 0], csd_data[:, 1].imag, bounds_error=False, fill_value="extrapolate")(freqs)
|
|
160
|
+
csd_complex = csd_real + 1j * csd_imag
|
|
161
|
+
self._csd_magnitude = np.abs(csd_complex)
|
|
162
|
+
self._csd_phase = np.angle(csd_complex)
|
|
163
|
+
|
|
164
|
+
def _compute_spectral_matrix_cholesky(self) -> coo_matrix:
|
|
165
|
+
"""Compute the Cholesky decomposition of the spectral matrix.
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
Sparse COO matrix containing the block-diagonal Cholesky decomposition.
|
|
169
|
+
"""
|
|
170
|
+
# Compute diagonal elements
|
|
171
|
+
d0 = self._psd * 0.25 / self._df
|
|
172
|
+
d1 = self._csd_magnitude * 0.25 / self._df * np.exp(-1j * self._csd_phase)
|
|
173
|
+
|
|
174
|
+
# Build Cholesky decomposition of the spectral matrix in block-diagonal form
|
|
175
|
+
spectral_matrix = np.empty((self._n_freq, self._n_det, self._n_det), dtype=np.complex128)
|
|
176
|
+
for n in range(self._n_freq):
|
|
177
|
+
submatrix = np.array(
|
|
178
|
+
[
|
|
179
|
+
[d0[n] if row == col else d1[n] if row < col else np.conj(d1[n]) for row in range(self._n_det)]
|
|
180
|
+
for col in range(self._n_det)
|
|
181
|
+
]
|
|
182
|
+
)
|
|
183
|
+
spectral_matrix[n, :, :] = cholesky(submatrix)
|
|
184
|
+
|
|
185
|
+
return block_diag(spectral_matrix, format="coo")
|
|
186
|
+
|
|
187
|
+
def _generate_single_realization(self) -> np.ndarray:
|
|
188
|
+
"""Generate a single noise realization in the time domain.
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
Time series array with shape (n_detectors, n_samples).
|
|
192
|
+
"""
|
|
193
|
+
if self.rng is None:
|
|
194
|
+
raise RuntimeError("Random number generator not initialized. Set seed in constructor.")
|
|
195
|
+
|
|
196
|
+
freq_series = np.zeros((self._n_det, self._frequency.size), dtype=np.complex128)
|
|
197
|
+
|
|
198
|
+
# Generate white noise and color it with the spectral matrix
|
|
199
|
+
white_strain = self.rng.standard_normal(self._n_freq * self._n_det) + 1j * self.rng.standard_normal(
|
|
200
|
+
self._n_freq * self._n_det
|
|
201
|
+
)
|
|
202
|
+
colored_strain = self._spectral_matrix.dot(white_strain)
|
|
203
|
+
|
|
204
|
+
# Split the frequency strain for each detector
|
|
205
|
+
freq_series[:, self._k_min : self._k_max] += np.transpose(
|
|
206
|
+
np.reshape(colored_strain, (self._n_freq, self._n_det))
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
# Transform each frequency strain into the time domain
|
|
210
|
+
time_series = np.fft.irfft(freq_series, n=self._n_samples, axis=1) * self._df * self._n_samples
|
|
211
|
+
|
|
212
|
+
return time_series
|
|
213
|
+
|
|
214
|
+
def _simulate(self, *args, **kwargs) -> TimeSeriesList:
|
|
215
|
+
"""Simulate correlated noise for all detectors.
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
TimeSeriesList containing a single TimeSeries with shape (n_detectors, n_samples).
|
|
219
|
+
"""
|
|
220
|
+
n_frame = int(self.duration.value * self.sampling_frequency.value)
|
|
221
|
+
|
|
222
|
+
# Generate the initial single noise realization and apply the final part of the window
|
|
223
|
+
strain_buffer = self._generate_single_realization()
|
|
224
|
+
strain_buffer[:, -self._n_overlap :] *= self._w0
|
|
225
|
+
|
|
226
|
+
# Extend the strain buffer until it has more valid data than a single frame
|
|
227
|
+
while strain_buffer.shape[-1] - self._n_overlap < n_frame:
|
|
228
|
+
new_strain = self._generate_single_realization()
|
|
229
|
+
new_strain[:, : self._n_overlap] *= self._w1
|
|
230
|
+
new_strain[:, -self._n_overlap :] *= self._w0
|
|
231
|
+
strain_buffer[:, -self._n_overlap :] += new_strain[:, : self._n_overlap]
|
|
232
|
+
strain_buffer = np.concatenate((strain_buffer, new_strain[:, self._n_overlap :]), axis=1)
|
|
233
|
+
|
|
234
|
+
# Extract the frame and create TimeSeries
|
|
235
|
+
data = strain_buffer[:, :n_frame]
|
|
236
|
+
|
|
237
|
+
return TimeSeriesList(
|
|
238
|
+
[TimeSeries(data=data, start_time=self.start_time, sampling_frequency=self.sampling_frequency)]
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
@property
|
|
242
|
+
def metadata(self) -> dict:
|
|
243
|
+
"""Get metadata including correlated noise configuration.
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
Dictionary containing metadata.
|
|
247
|
+
"""
|
|
248
|
+
meta = super().metadata
|
|
249
|
+
meta["correlated_noise"] = {
|
|
250
|
+
"arguments": {
|
|
251
|
+
"psd_file": str(self.psd_file),
|
|
252
|
+
"csd_file": str(self.csd_file),
|
|
253
|
+
"low_frequency_cutoff": self.low_frequency_cutoff,
|
|
254
|
+
"high_frequency_cutoff": self.high_frequency_cutoff,
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return meta
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""Stationary Gaussian noise simulator using Bilby."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
import pycbc.psd
|
|
10
|
+
from pycbc.noise import noise_from_psd
|
|
11
|
+
from pycbc.types.frequencyseries import FrequencySeries
|
|
12
|
+
|
|
13
|
+
from gwsim.data.time_series.time_series import TimeSeries
|
|
14
|
+
from gwsim.data.time_series.time_series_list import TimeSeriesList
|
|
15
|
+
from gwsim.noise.stationary_gaussian import StationaryGaussianNoiseSimulator
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger("gwsim")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class PyCBCStationaryGaussianNoiseSimulator(
|
|
21
|
+
StationaryGaussianNoiseSimulator
|
|
22
|
+
): # pylint: disable=too-many-ancestors, duplicate-code
|
|
23
|
+
"""Stationary Gaussian noise simulator using Bilby."""
|
|
24
|
+
|
|
25
|
+
def __init__( # pylint: disable=too-many-arguments, too-many-positional-arguments
|
|
26
|
+
self,
|
|
27
|
+
frequency_array: np.ndarray[Any, np.dtype[Any]] | None = None,
|
|
28
|
+
psd_array: np.ndarray[Any, np.dtype[Any]] | None = None,
|
|
29
|
+
psd_file: str | None = None,
|
|
30
|
+
label: str | None = None,
|
|
31
|
+
low_frequency_cutoff: float = 5.0,
|
|
32
|
+
sampling_frequency: float = 4096,
|
|
33
|
+
duration: float = 4,
|
|
34
|
+
start_time: float = 0,
|
|
35
|
+
max_samples: int | None = None,
|
|
36
|
+
seed: int | None = None,
|
|
37
|
+
detectors: list[str] | None = None,
|
|
38
|
+
**kwargs,
|
|
39
|
+
):
|
|
40
|
+
"""Initialize Bilby stationary Gaussian noise simulator.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
frequency_array (np.ndarray): Frequency array for the PSD.
|
|
44
|
+
psd_array (np.ndarray): PSD values corresponding to the frequency array.
|
|
45
|
+
psd_file (str): Path to a file containing the PSD.
|
|
46
|
+
label (str): Label for a predefined PyCBC PSD.
|
|
47
|
+
low_frequency_cutoff (float): Low frequency cutoff for the PSD. Default is 5.0 Hz.
|
|
48
|
+
sampling_frequency (float): Sampling frequency in Hz. Default is 4096.
|
|
49
|
+
duration (float): Duration of each segment in seconds. Default is 4.
|
|
50
|
+
start_time (float): Start time in GPS seconds. Default is 0.
|
|
51
|
+
max_samples (int | None): Maximum number of samples. None means infinite.
|
|
52
|
+
seed (int | None): Random seed. If None, RNG is not initialized.
|
|
53
|
+
detectors (list[str] | None): List of detector names. Default is None.
|
|
54
|
+
**kwargs: Additional arguments.
|
|
55
|
+
"""
|
|
56
|
+
super().__init__(
|
|
57
|
+
sampling_frequency=sampling_frequency,
|
|
58
|
+
duration=duration,
|
|
59
|
+
start_time=start_time,
|
|
60
|
+
max_samples=max_samples,
|
|
61
|
+
seed=seed,
|
|
62
|
+
detectors=detectors,
|
|
63
|
+
**kwargs,
|
|
64
|
+
)
|
|
65
|
+
self.frequency_array = frequency_array
|
|
66
|
+
self.psd_array = psd_array
|
|
67
|
+
self.psd_file = psd_file
|
|
68
|
+
self.label = label
|
|
69
|
+
self.low_frequency_cutoff = low_frequency_cutoff
|
|
70
|
+
self._setup_psd()
|
|
71
|
+
|
|
72
|
+
def _setup_psd(self) -> None:
|
|
73
|
+
if self.frequency_array is not None and self.psd_array is not None:
|
|
74
|
+
logger.info("Setting PSD values below low_frequency_cutoff = %s to zero.", self.low_frequency_cutoff)
|
|
75
|
+
self.psd_array[self.frequency_array < self.low_frequency_cutoff] = 0.0
|
|
76
|
+
self.psd = FrequencySeries(
|
|
77
|
+
initial_array=self.psd_array, delta_f=self.frequency_array[1] - self.frequency_array[0]
|
|
78
|
+
)
|
|
79
|
+
elif self.psd_file is not None:
|
|
80
|
+
data = np.loadtxt(self.psd_file)
|
|
81
|
+
frequency_array = data[:, 0]
|
|
82
|
+
psd_values = data[:, 1]
|
|
83
|
+
logger.info("Setting PSD values below low_frequency_cutoff = %s to zero.", self.low_frequency_cutoff)
|
|
84
|
+
psd_values[frequency_array < self.low_frequency_cutoff] = 0.0
|
|
85
|
+
self.psd = FrequencySeries(initial_array=psd_values, delta_f=data[1, 0] - data[0, 0])
|
|
86
|
+
elif self.label is not None:
|
|
87
|
+
self.psd = pycbc.psd.from_string(
|
|
88
|
+
psd_name=self.label,
|
|
89
|
+
length=int(self.duration * self.sampling_frequency // 2 + 1),
|
|
90
|
+
delta_f=1.0 / self.duration.value,
|
|
91
|
+
low_freq_cutoff=self.low_frequency_cutoff,
|
|
92
|
+
)
|
|
93
|
+
else:
|
|
94
|
+
raise ValueError("Either frequency_array and psd_array or psd_file must be provided.")
|
|
95
|
+
|
|
96
|
+
def _simulate(self, *args, **kwargs) -> TimeSeriesList:
|
|
97
|
+
"""Simulate a noise segment.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
np.ndarray: Simulated noise segment as a numpy array.
|
|
101
|
+
"""
|
|
102
|
+
if self.rng is None:
|
|
103
|
+
raise RuntimeError("Random number generator not initialized. Set seed in constructor.")
|
|
104
|
+
data: np.ndarray = noise_from_psd(
|
|
105
|
+
length=int(self.duration * self.sampling_frequency),
|
|
106
|
+
delta_t=1.0 / self.sampling_frequency.value,
|
|
107
|
+
psd=self.psd,
|
|
108
|
+
seed=int(self.rng.integers(0, 2**31 - 1)),
|
|
109
|
+
).numpy()[None, :]
|
|
110
|
+
return TimeSeriesList(
|
|
111
|
+
[TimeSeries(data=data, start_time=self.start_time, sampling_frequency=self.sampling_frequency)]
|
|
112
|
+
)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Colored noise simulator implementation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from gwsim.noise.base import NoiseSimulator
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class StationaryGaussianNoiseSimulator(NoiseSimulator): # pylint: disable=duplicate-code
|
|
9
|
+
"""Stationary Gaussian noise simulator.
|
|
10
|
+
|
|
11
|
+
Generates noise from a specified power spectral density.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
sampling_frequency: float = 4096,
|
|
17
|
+
duration: float = 4,
|
|
18
|
+
start_time: float = 0,
|
|
19
|
+
max_samples: int | None = None,
|
|
20
|
+
seed: int | None = None,
|
|
21
|
+
detectors: list[str] | None = None,
|
|
22
|
+
**kwargs,
|
|
23
|
+
):
|
|
24
|
+
"""Initialize stationary Gaussian noise simulator.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
psd: Path to PSD file or numpy array with PSD values, or label of PSD.
|
|
28
|
+
sampling_frequency: Sampling frequency in Hz. Default is 4096.
|
|
29
|
+
duration: Duration of each segment in seconds. Default is 4.
|
|
30
|
+
start_time: Start time in GPS seconds. Default is 0.
|
|
31
|
+
max_samples: Maximum number of samples. None means infinite.
|
|
32
|
+
seed: Random seed. If None, RNG is not initialized.
|
|
33
|
+
detectors: List of detector names. Default is None.
|
|
34
|
+
**kwargs: Additional arguments.
|
|
35
|
+
"""
|
|
36
|
+
super().__init__( # pylint: disable=duplicate-code
|
|
37
|
+
sampling_frequency=sampling_frequency,
|
|
38
|
+
duration=duration,
|
|
39
|
+
start_time=start_time,
|
|
40
|
+
max_samples=max_samples,
|
|
41
|
+
seed=seed,
|
|
42
|
+
detectors=detectors,
|
|
43
|
+
**kwargs,
|
|
44
|
+
)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""White noise simulator implementation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
from gwsim.noise.base import NoiseSimulator
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class WhiteNoiseSimulator(NoiseSimulator):
|
|
11
|
+
"""White noise simulator."""
|
|
12
|
+
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
loc: float,
|
|
16
|
+
scale: float,
|
|
17
|
+
sampling_frequency: float,
|
|
18
|
+
duration: float,
|
|
19
|
+
start_time: float = 0,
|
|
20
|
+
max_samples: int | None = None,
|
|
21
|
+
seed: int | None = None,
|
|
22
|
+
**kwargs,
|
|
23
|
+
):
|
|
24
|
+
"""Initialize the white noise simulator.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
loc: Mean of the normal distribution.
|
|
28
|
+
scale: Standard deviation of the normal distribution.
|
|
29
|
+
sampling_frequency: Sampling frequency of the noise in Hz.
|
|
30
|
+
duration: Duration of each noise segment in seconds.
|
|
31
|
+
start_time: Start time of the first noise segment in GPS seconds. Default is 0
|
|
32
|
+
max_samples: Maximum number of samples to generate. None means infinite.
|
|
33
|
+
seed: Seed for the random number generator. If None, the RNG is not initialized.
|
|
34
|
+
**kwargs: Additional arguments absorbed by subclasses and mixins.
|
|
35
|
+
"""
|
|
36
|
+
super().__init__(
|
|
37
|
+
sampling_frequency=sampling_frequency,
|
|
38
|
+
duration=duration,
|
|
39
|
+
start_time=start_time,
|
|
40
|
+
max_samples=max_samples,
|
|
41
|
+
seed=seed,
|
|
42
|
+
**kwargs,
|
|
43
|
+
)
|
|
44
|
+
self.loc = loc
|
|
45
|
+
self.scale = scale
|
|
46
|
+
|
|
47
|
+
def next(self) -> np.ndarray:
|
|
48
|
+
"""Generate the next batch of white noise data."""
|
|
49
|
+
if self.rng is None:
|
|
50
|
+
raise RuntimeError("Random number generator not initialized. Set seed in constructor.")
|
|
51
|
+
return self.rng.normal(loc=self.loc, scale=self.scale, size=int(self.duration * self.sampling_frequency))
|
|
File without changes
|