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,269 @@
|
|
|
1
|
+
"""Zenodo publishing client."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import requests
|
|
10
|
+
from requests import Response
|
|
11
|
+
|
|
12
|
+
from gwsim.utils.retry import retry_on_failure
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_deposition_id_from_doi(doi: str) -> tuple[str, bool]:
|
|
16
|
+
"""Extract deposition ID from a Zenodo DOI.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
doi: The DOI string.
|
|
20
|
+
|
|
21
|
+
returns:
|
|
22
|
+
A tuple containing the deposition ID and a boolean indicating if it's from sandbox.
|
|
23
|
+
"""
|
|
24
|
+
parts = doi.split(".")
|
|
25
|
+
deposition_id = parts[1]
|
|
26
|
+
if parts[0] == "10.5072/zenodo":
|
|
27
|
+
sandbox = True
|
|
28
|
+
elif parts[0] == "10.5281/zenodo":
|
|
29
|
+
sandbox = False
|
|
30
|
+
else:
|
|
31
|
+
raise ValueError(f"Invalid Zenodo DOI: {doi}")
|
|
32
|
+
|
|
33
|
+
return deposition_id, sandbox
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ZenodoClient:
|
|
37
|
+
"""Client for interacting with Zenodo API (production and sandbox)."""
|
|
38
|
+
|
|
39
|
+
def __init__(self, access_token: str, sandbox: bool = False):
|
|
40
|
+
"""Initialize the Zenodo client.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
access_token: Zenodo API access token.
|
|
44
|
+
sandbox: Whether to use the sandbox environment. Default is False.
|
|
45
|
+
"""
|
|
46
|
+
self.access_token = access_token
|
|
47
|
+
self.sandbox = sandbox
|
|
48
|
+
self.base_url = "https://sandbox.zenodo.org/api/" if sandbox else "https://zenodo.org/api/"
|
|
49
|
+
|
|
50
|
+
self.headers = {
|
|
51
|
+
"Authorization": f"Bearer {self.access_token}",
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
@retry_on_failure()
|
|
55
|
+
def _request(self, method: str, url: str, headers: dict, timeout: int = 60, **kwargs) -> Response:
|
|
56
|
+
"""Make a request to the Zenodo API.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
method: HTTP method (GET, POST, PUT, DELETE).
|
|
60
|
+
url: URL to make the request to.
|
|
61
|
+
headers: Headers to include in the request.
|
|
62
|
+
timeout: Timeout for the request in seconds. Default is 60.
|
|
63
|
+
**kwargs: Additional arguments to pass to requests.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Response object.
|
|
67
|
+
"""
|
|
68
|
+
response = requests.request(method, url, headers=headers, timeout=timeout, **kwargs)
|
|
69
|
+
response.raise_for_status()
|
|
70
|
+
return response
|
|
71
|
+
|
|
72
|
+
def create_deposition(self, metadata: dict[str, Any] | None = None, timeout=60) -> dict[str, Any]:
|
|
73
|
+
"""Create a new deposition.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
metadata: Optional metadata dictionary for the deposition.
|
|
77
|
+
timeout: Timeout for the request in seconds. Default is 60.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Response JSON as a dictionary.
|
|
81
|
+
"""
|
|
82
|
+
data = {"metadata": metadata} if metadata else {}
|
|
83
|
+
|
|
84
|
+
response: Response = self._request(
|
|
85
|
+
"POST",
|
|
86
|
+
f"{self.base_url}deposit/depositions",
|
|
87
|
+
headers={"Content-Type": "application/json", **self.headers},
|
|
88
|
+
timeout=timeout,
|
|
89
|
+
json=data,
|
|
90
|
+
)
|
|
91
|
+
return response.json()
|
|
92
|
+
|
|
93
|
+
def upload_file(
|
|
94
|
+
self, deposition_id: str, file_path: Path, timeout=300, auto_timeout: bool = True
|
|
95
|
+
) -> dict[str, Any]:
|
|
96
|
+
"""Upload a file to a deposition.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
deposition_id: ID of the deposition to upload the file to.
|
|
100
|
+
file_path: Path to the file to upload.
|
|
101
|
+
timeout: Timeout for the request in seconds. Default is 300.
|
|
102
|
+
auto_timeout: Whether to automatically adjust timeout based on file size. Default is True.
|
|
103
|
+
If True, the timeout is set to max(timeout, file_size_in_MB * 10).
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
Response JSON as a dictionary.
|
|
107
|
+
"""
|
|
108
|
+
deposition = self.get_deposition(deposition_id)
|
|
109
|
+
|
|
110
|
+
bucket = deposition["links"]["bucket"]
|
|
111
|
+
|
|
112
|
+
if auto_timeout:
|
|
113
|
+
# Get the size of the file in MB
|
|
114
|
+
file_size = file_path.stat().st_size / (1024 * 1024)
|
|
115
|
+
|
|
116
|
+
timeout = max(timeout, int(file_size * 10)) # 10 seconds per MB, minimum 300 seconds
|
|
117
|
+
|
|
118
|
+
with file_path.open("rb") as f:
|
|
119
|
+
response: Response = self._request(
|
|
120
|
+
"PUT",
|
|
121
|
+
f"{bucket}/{file_path.name}",
|
|
122
|
+
headers=self.headers,
|
|
123
|
+
timeout=timeout,
|
|
124
|
+
data=f,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
return response.json()
|
|
128
|
+
|
|
129
|
+
def update_metadata(self, deposition_id: str, metadata: dict[str, Any], timeout: int = 60) -> dict[str, Any]:
|
|
130
|
+
"""Update metadata for a deposition.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
deposition_id: ID of the deposition to update.
|
|
134
|
+
metadata: Metadata dictionary to update.
|
|
135
|
+
timeout: Timeout for the request in seconds. Default is 60.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
Response JSON as a dictionary.
|
|
139
|
+
"""
|
|
140
|
+
data = {"metadata": metadata}
|
|
141
|
+
response: Response = self._request(
|
|
142
|
+
"PUT",
|
|
143
|
+
f"{self.base_url}deposit/depositions/{deposition_id}",
|
|
144
|
+
headers={"Content-Type": "application/json", **self.headers},
|
|
145
|
+
timeout=timeout,
|
|
146
|
+
data=json.dumps(data),
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
return response.json()
|
|
150
|
+
|
|
151
|
+
def publish_deposition(self, deposition_id: str, timeout: int = 300) -> dict[str, Any]:
|
|
152
|
+
"""Publish a deposition.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
deposition_id: ID of the deposition to publish.
|
|
156
|
+
timeout: Timeout for the request in seconds. Default is 300.
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
Response JSON as a dictionary.
|
|
160
|
+
"""
|
|
161
|
+
response: Response = self._request(
|
|
162
|
+
"POST",
|
|
163
|
+
f"{self.base_url}deposit/depositions/{deposition_id}/actions/publish",
|
|
164
|
+
headers=self.headers,
|
|
165
|
+
timeout=timeout,
|
|
166
|
+
)
|
|
167
|
+
return response.json()
|
|
168
|
+
|
|
169
|
+
def get_deposition(self, deposition_id: str, timeout: int = 60) -> dict[str, Any]:
|
|
170
|
+
"""Retrieve a deposition's details.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
deposition_id: ID of the deposition to retrieve.
|
|
174
|
+
timeout: Timeout for the request in seconds. Default is 60.
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
Response JSON as a dictionary.
|
|
178
|
+
"""
|
|
179
|
+
response: Response = self._request(
|
|
180
|
+
"GET",
|
|
181
|
+
f"{self.base_url}deposit/depositions/{deposition_id}",
|
|
182
|
+
headers={"Content-Type": "application/json", **self.headers},
|
|
183
|
+
timeout=timeout,
|
|
184
|
+
)
|
|
185
|
+
return response.json()
|
|
186
|
+
|
|
187
|
+
def download_file(
|
|
188
|
+
self,
|
|
189
|
+
deposition_id: str,
|
|
190
|
+
filename: str,
|
|
191
|
+
output_path: Path,
|
|
192
|
+
is_draft: bool = False,
|
|
193
|
+
timeout: int = 300,
|
|
194
|
+
file_size_in_mb: int | None = None,
|
|
195
|
+
) -> dict[str, Any]:
|
|
196
|
+
"""Download a file from Zenodo.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
deposition_id: ID of the deposition.
|
|
200
|
+
filename: Name of the file to download.
|
|
201
|
+
output_path: Path to save the downloaded file.
|
|
202
|
+
is_draft: Whether the file is in a draft deposition. Default is False.
|
|
203
|
+
timeout: Timeout for the request in seconds. Default is 300.
|
|
204
|
+
file_size_in_mb: Optional size of the file in MB to adjust timeout. If provided
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
Response JSON as a dictionary.
|
|
208
|
+
"""
|
|
209
|
+
if is_draft:
|
|
210
|
+
deposition_id += "/draft"
|
|
211
|
+
|
|
212
|
+
file_url = f"{self.base_url}records/{deposition_id}/files/{filename}"
|
|
213
|
+
|
|
214
|
+
if file_size_in_mb is not None:
|
|
215
|
+
timeout = max(timeout, int(file_size_in_mb * 10)) # 10 seconds per MB
|
|
216
|
+
|
|
217
|
+
response: Response = self._request(
|
|
218
|
+
"GET",
|
|
219
|
+
file_url,
|
|
220
|
+
headers={"Content-Type": "application/json", **self.headers},
|
|
221
|
+
timeout=timeout,
|
|
222
|
+
stream=True,
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
# Atomic write to avoid incomplete files
|
|
226
|
+
output_path_tmp = output_path.with_suffix(".tmp")
|
|
227
|
+
with output_path_tmp.open("wb") as f:
|
|
228
|
+
for chunk in response.iter_content(chunk_size=8192):
|
|
229
|
+
f.write(chunk)
|
|
230
|
+
output_path_tmp.rename(output_path)
|
|
231
|
+
return response.json()
|
|
232
|
+
|
|
233
|
+
def list_depositions(self, status: str = "published", timeout: int = 60) -> list[dict[str, Any]]:
|
|
234
|
+
"""List all depositions for the authenticated user.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
status: Filter by deposition status ('draft', 'unsubmitted', 'published'). Default is 'published'.
|
|
238
|
+
timeout: Timeout for the request in seconds. Default is 60.
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
List of deposition dictionaries.
|
|
242
|
+
"""
|
|
243
|
+
params = {"status": status}
|
|
244
|
+
response = self._request(
|
|
245
|
+
"GET",
|
|
246
|
+
f"{self.base_url}deposit/depositions",
|
|
247
|
+
headers={"Content-Type": "application/json", **self.headers},
|
|
248
|
+
timeout=timeout,
|
|
249
|
+
params=params,
|
|
250
|
+
)
|
|
251
|
+
return response.json()
|
|
252
|
+
|
|
253
|
+
def delete_deposition(self, deposition_id: str, timeout: int = 60) -> dict[str, Any]:
|
|
254
|
+
"""Delete a deposition.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
deposition_id: ID of the deposition to delete.
|
|
258
|
+
timeout: Timeout for the request in seconds. Default is 60.
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
Response JSON as a dictionary.
|
|
262
|
+
"""
|
|
263
|
+
response = self._request(
|
|
264
|
+
"DELETE",
|
|
265
|
+
f"{self.base_url}deposit/depositions/{deposition_id}",
|
|
266
|
+
headers=self.headers,
|
|
267
|
+
timeout=timeout,
|
|
268
|
+
)
|
|
269
|
+
return response
|
gwsim/signal/__init__.py
ADDED
gwsim/signal/base.py
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Base class for signal simulators."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, cast
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
|
|
12
|
+
from gwsim.data.time_series.time_series_list import TimeSeriesList
|
|
13
|
+
from gwsim.mixin.detector import DetectorMixin
|
|
14
|
+
from gwsim.mixin.population_reader import PopulationReaderMixin
|
|
15
|
+
from gwsim.mixin.time_series import TimeSeriesMixin
|
|
16
|
+
from gwsim.mixin.waveform import WaveformMixin
|
|
17
|
+
from gwsim.simulator.base import Simulator
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger("gwsim")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SignalSimulator(PopulationReaderMixin, WaveformMixin, TimeSeriesMixin, DetectorMixin, Simulator):
|
|
23
|
+
"""Base class for signal simulators."""
|
|
24
|
+
|
|
25
|
+
def __init__( # pylint: disable=too-many-arguments,too-many-positional-arguments,too-many-locals
|
|
26
|
+
self,
|
|
27
|
+
population_file: str | Path,
|
|
28
|
+
population_file_type: str = "pycbc",
|
|
29
|
+
waveform_model: str | Callable = "IMRPhenomXPHM",
|
|
30
|
+
waveform_arguments: dict[str, Any] | None = None,
|
|
31
|
+
start_time: int = 0,
|
|
32
|
+
duration: float = 1024,
|
|
33
|
+
sampling_frequency: float = 4096,
|
|
34
|
+
max_samples: int | None = None,
|
|
35
|
+
dtype: type = np.float64,
|
|
36
|
+
detectors: list[str] | None = None,
|
|
37
|
+
minimum_frequency: float = 5,
|
|
38
|
+
**kwargs,
|
|
39
|
+
) -> None:
|
|
40
|
+
"""Initialize the base signal simulator.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
population_file: Path to the population file.
|
|
44
|
+
population_file_type: Type of the population file (e.g., 'pycbc').
|
|
45
|
+
waveform_model: Name (from registry) or callable for waveform generation.
|
|
46
|
+
waveform_arguments: Fixed parameters to pass to waveform model.
|
|
47
|
+
start_time: Start time of the first signal segment in GPS seconds. Default is 0.
|
|
48
|
+
duration: Duration of each signal segment in seconds. Default is 1024.
|
|
49
|
+
sampling_frequency: Sampling frequency of the signals in Hz. Default is 4096.
|
|
50
|
+
max_samples: Maximum number of samples to generate. None means infinite.
|
|
51
|
+
dtype: Data type for the time series data. Default is np.float64.
|
|
52
|
+
detectors: List of detector names. Default is None.
|
|
53
|
+
minimum_frequency: Minimum GW frequency for waveform generation. Default is 5 Hz.
|
|
54
|
+
**kwargs: Additional arguments absorbed by subclasses and mixins.
|
|
55
|
+
"""
|
|
56
|
+
waveform_arguments = waveform_arguments or {}
|
|
57
|
+
required_waveform_arguments = {
|
|
58
|
+
"minimum_frequency": minimum_frequency,
|
|
59
|
+
"sampling_frequency": sampling_frequency,
|
|
60
|
+
}
|
|
61
|
+
for key, value in required_waveform_arguments.items():
|
|
62
|
+
if key not in waveform_arguments:
|
|
63
|
+
logger.info("%s not specified in waveform_arguments; setting to %s", key, value)
|
|
64
|
+
waveform_arguments[key] = value
|
|
65
|
+
|
|
66
|
+
super().__init__(
|
|
67
|
+
population_file=population_file,
|
|
68
|
+
population_file_type=population_file_type,
|
|
69
|
+
waveform_model=waveform_model,
|
|
70
|
+
waveform_arguments=waveform_arguments,
|
|
71
|
+
detectors=detectors,
|
|
72
|
+
start_time=start_time,
|
|
73
|
+
duration=duration,
|
|
74
|
+
sampling_frequency=sampling_frequency,
|
|
75
|
+
max_samples=max_samples,
|
|
76
|
+
dtype=dtype,
|
|
77
|
+
**kwargs,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
def _simulate(self, *args, **kwargs) -> TimeSeriesList:
|
|
81
|
+
"""Simulate signals for the current segment.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
TimeSeriesList: List of simulated signals.
|
|
85
|
+
"""
|
|
86
|
+
output = []
|
|
87
|
+
|
|
88
|
+
while True:
|
|
89
|
+
# Get the next injection parameters
|
|
90
|
+
parameters = self.get_next_injection_parameters()
|
|
91
|
+
|
|
92
|
+
# If the parameters are None, break the loop
|
|
93
|
+
if parameters is None:
|
|
94
|
+
break
|
|
95
|
+
|
|
96
|
+
# Get the polarizations
|
|
97
|
+
polarizations = self.waveform_factory.generate(
|
|
98
|
+
waveform_model=self.waveform_model, parameters=parameters, **self.waveform_arguments
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# Project onto detectors
|
|
102
|
+
strain = self.project_polarizations(
|
|
103
|
+
polarizations=polarizations,
|
|
104
|
+
right_ascension=parameters["right_ascension"],
|
|
105
|
+
declination=parameters["declination"],
|
|
106
|
+
polarization_angle=parameters["polarization_angle"],
|
|
107
|
+
**self.waveform_arguments,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# Register the parameters
|
|
111
|
+
strain.metadata.update({"injection_parameters": parameters})
|
|
112
|
+
|
|
113
|
+
output.append(strain)
|
|
114
|
+
|
|
115
|
+
# Check whether the start time of the strain is at or after the end time of the current segment
|
|
116
|
+
if strain.start_time >= self.end_time:
|
|
117
|
+
break
|
|
118
|
+
return TimeSeriesList(output)
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def metadata(self) -> dict:
|
|
122
|
+
"""Get the metadata of the simulator.
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
Metadata dictionary.
|
|
126
|
+
"""
|
|
127
|
+
meta = super().metadata
|
|
128
|
+
return meta
|
|
129
|
+
|
|
130
|
+
def update_state(self) -> None:
|
|
131
|
+
"""Update internal state after each sample generation.
|
|
132
|
+
|
|
133
|
+
This method can be overridden by subclasses to update any internal state
|
|
134
|
+
after generating a sample. The default implementation does nothing.
|
|
135
|
+
"""
|
|
136
|
+
self.counter = cast(int, self.counter) + 1
|
|
137
|
+
self.start_time += self.duration
|
gwsim/signal/cbc.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Compact Binary Coalescence (CBC) signal simulation module."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
from gwsim.signal.base import SignalSimulator
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CBCSignalSimulator(SignalSimulator):
|
|
15
|
+
"""CBC Signal Simulator class."""
|
|
16
|
+
|
|
17
|
+
def __init__( # pylint: disable=too-many-arguments,too-many-positional-arguments
|
|
18
|
+
self,
|
|
19
|
+
population_file: str | Path,
|
|
20
|
+
population_file_type: str = "pycbc",
|
|
21
|
+
waveform_model: str | Callable = "IMRPhenomXPHM",
|
|
22
|
+
waveform_arguments: dict[str, Any] | None = None,
|
|
23
|
+
start_time: int = 0,
|
|
24
|
+
duration: float = 1024,
|
|
25
|
+
sampling_frequency: float = 4096,
|
|
26
|
+
max_samples: int | None = None,
|
|
27
|
+
dtype: type = np.float64,
|
|
28
|
+
detectors: list[str] | None = None,
|
|
29
|
+
minimum_frequency: float = 5,
|
|
30
|
+
**kwargs,
|
|
31
|
+
) -> None:
|
|
32
|
+
"""Initialize the CBC signal simulator.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
population_file: Path to the population file.
|
|
36
|
+
population_file_type: Type of the population file (e.g., 'pycbc').
|
|
37
|
+
waveform_model: Name (from registry) or callable for waveform generation.
|
|
38
|
+
waveform_arguments: Fixed parameters to pass to waveform model.
|
|
39
|
+
start_time: Start time of the first signal segment in GPS seconds. Default is 0.
|
|
40
|
+
duration: Duration of each signal segment in seconds. Default is 1024.
|
|
41
|
+
sampling_frequency: Sampling frequency of the signals in Hz. Default is 4096.
|
|
42
|
+
max_samples: Maximum number of samples to generate. None means infinite.
|
|
43
|
+
dtype: Data type for the time series data. Default is np.float64.
|
|
44
|
+
detectors: List of detector names. Default is None.
|
|
45
|
+
minimum_frequency: Minimum GW frequency for waveform generation. Default is 5 Hz.
|
|
46
|
+
**kwargs: Additional arguments absorbed by subclasses and mixins.
|
|
47
|
+
"""
|
|
48
|
+
super().__init__(
|
|
49
|
+
population_file=population_file,
|
|
50
|
+
population_file_type=population_file_type,
|
|
51
|
+
waveform_model=waveform_model,
|
|
52
|
+
waveform_arguments=waveform_arguments,
|
|
53
|
+
start_time=start_time,
|
|
54
|
+
duration=duration,
|
|
55
|
+
sampling_frequency=sampling_frequency,
|
|
56
|
+
max_samples=max_samples,
|
|
57
|
+
dtype=dtype,
|
|
58
|
+
detectors=detectors,
|
|
59
|
+
minimum_frequency=minimum_frequency,
|
|
60
|
+
**kwargs,
|
|
61
|
+
)
|