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.
Files changed (103) hide show
  1. gwsim/__init__.py +11 -0
  2. gwsim/__main__.py +8 -0
  3. gwsim/cli/__init__.py +0 -0
  4. gwsim/cli/config.py +88 -0
  5. gwsim/cli/default_config.py +56 -0
  6. gwsim/cli/main.py +101 -0
  7. gwsim/cli/merge.py +150 -0
  8. gwsim/cli/repository/__init__.py +0 -0
  9. gwsim/cli/repository/create.py +91 -0
  10. gwsim/cli/repository/delete.py +51 -0
  11. gwsim/cli/repository/download.py +54 -0
  12. gwsim/cli/repository/list_depositions.py +63 -0
  13. gwsim/cli/repository/main.py +38 -0
  14. gwsim/cli/repository/metadata/__init__.py +0 -0
  15. gwsim/cli/repository/metadata/main.py +24 -0
  16. gwsim/cli/repository/metadata/update.py +58 -0
  17. gwsim/cli/repository/publish.py +52 -0
  18. gwsim/cli/repository/upload.py +74 -0
  19. gwsim/cli/repository/utils.py +47 -0
  20. gwsim/cli/repository/verify.py +61 -0
  21. gwsim/cli/simulate.py +220 -0
  22. gwsim/cli/simulate_utils.py +596 -0
  23. gwsim/cli/utils/__init__.py +85 -0
  24. gwsim/cli/utils/checkpoint.py +178 -0
  25. gwsim/cli/utils/config.py +347 -0
  26. gwsim/cli/utils/hash.py +23 -0
  27. gwsim/cli/utils/retry.py +62 -0
  28. gwsim/cli/utils/simulation_plan.py +439 -0
  29. gwsim/cli/utils/template.py +56 -0
  30. gwsim/cli/utils/utils.py +149 -0
  31. gwsim/cli/validate.py +255 -0
  32. gwsim/data/__init__.py +8 -0
  33. gwsim/data/serialize/__init__.py +9 -0
  34. gwsim/data/serialize/decoder.py +59 -0
  35. gwsim/data/serialize/encoder.py +44 -0
  36. gwsim/data/serialize/serializable.py +33 -0
  37. gwsim/data/time_series/__init__.py +3 -0
  38. gwsim/data/time_series/inject.py +104 -0
  39. gwsim/data/time_series/time_series.py +355 -0
  40. gwsim/data/time_series/time_series_list.py +182 -0
  41. gwsim/detector/__init__.py +8 -0
  42. gwsim/detector/base.py +156 -0
  43. gwsim/detector/detectors/E1_2L_Aligned_Sardinia.interferometer +22 -0
  44. gwsim/detector/detectors/E1_2L_Misaligned_Sardinia.interferometer +22 -0
  45. gwsim/detector/detectors/E1_Triangle_EMR.interferometer +19 -0
  46. gwsim/detector/detectors/E1_Triangle_Sardinia.interferometer +19 -0
  47. gwsim/detector/detectors/E2_2L_Aligned_EMR.interferometer +22 -0
  48. gwsim/detector/detectors/E2_2L_Misaligned_EMR.interferometer +22 -0
  49. gwsim/detector/detectors/E2_Triangle_EMR.interferometer +19 -0
  50. gwsim/detector/detectors/E2_Triangle_Sardinia.interferometer +19 -0
  51. gwsim/detector/detectors/E3_Triangle_EMR.interferometer +19 -0
  52. gwsim/detector/detectors/E3_Triangle_Sardinia.interferometer +19 -0
  53. gwsim/detector/noise_curves/ET_10_HF_psd.txt +3000 -0
  54. gwsim/detector/noise_curves/ET_10_full_cryo_psd.txt +3000 -0
  55. gwsim/detector/noise_curves/ET_15_HF_psd.txt +3000 -0
  56. gwsim/detector/noise_curves/ET_15_full_cryo_psd.txt +3000 -0
  57. gwsim/detector/noise_curves/ET_20_HF_psd.txt +3000 -0
  58. gwsim/detector/noise_curves/ET_20_full_cryo_psd.txt +3000 -0
  59. gwsim/detector/noise_curves/ET_D_psd.txt +3000 -0
  60. gwsim/detector/utils.py +90 -0
  61. gwsim/glitch/__init__.py +7 -0
  62. gwsim/glitch/base.py +69 -0
  63. gwsim/mixin/__init__.py +8 -0
  64. gwsim/mixin/detector.py +203 -0
  65. gwsim/mixin/gwf.py +192 -0
  66. gwsim/mixin/population_reader.py +175 -0
  67. gwsim/mixin/randomness.py +107 -0
  68. gwsim/mixin/time_series.py +295 -0
  69. gwsim/mixin/waveform.py +47 -0
  70. gwsim/noise/__init__.py +19 -0
  71. gwsim/noise/base.py +134 -0
  72. gwsim/noise/bilby_stationary_gaussian.py +117 -0
  73. gwsim/noise/colored_noise.py +275 -0
  74. gwsim/noise/correlated_noise.py +257 -0
  75. gwsim/noise/pycbc_stationary_gaussian.py +112 -0
  76. gwsim/noise/stationary_gaussian.py +44 -0
  77. gwsim/noise/white_noise.py +51 -0
  78. gwsim/repository/__init__.py +0 -0
  79. gwsim/repository/zenodo.py +269 -0
  80. gwsim/signal/__init__.py +11 -0
  81. gwsim/signal/base.py +137 -0
  82. gwsim/signal/cbc.py +61 -0
  83. gwsim/simulator/__init__.py +7 -0
  84. gwsim/simulator/base.py +315 -0
  85. gwsim/simulator/state.py +85 -0
  86. gwsim/utils/__init__.py +11 -0
  87. gwsim/utils/datetime_parser.py +44 -0
  88. gwsim/utils/et_2l_geometry.py +165 -0
  89. gwsim/utils/io.py +167 -0
  90. gwsim/utils/log.py +145 -0
  91. gwsim/utils/population.py +48 -0
  92. gwsim/utils/random.py +69 -0
  93. gwsim/utils/retry.py +75 -0
  94. gwsim/utils/triangular_et_geometry.py +164 -0
  95. gwsim/version.py +7 -0
  96. gwsim/waveform/__init__.py +7 -0
  97. gwsim/waveform/factory.py +83 -0
  98. gwsim/waveform/pycbc_wrapper.py +37 -0
  99. gwsim-0.1.0.dist-info/METADATA +157 -0
  100. gwsim-0.1.0.dist-info/RECORD +103 -0
  101. gwsim-0.1.0.dist-info/WHEEL +4 -0
  102. gwsim-0.1.0.dist-info/entry_points.txt +2 -0
  103. 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