sxs 2023.3.1__py3-none-any.whl → 2024.0.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.
- sxs/__init__.py +5 -5
- sxs/__version__.py +1 -1
- sxs/catalog/catalog.py +10 -1
- sxs/handlers.py +150 -48
- sxs/horizons/__init__.py +3 -3
- sxs/juliapkg.json +2 -2
- sxs/simulations/__init__.py +2 -0
- sxs/simulations/simulation.py +521 -0
- sxs/simulations/simulations.py +369 -0
- sxs/time_series.py +59 -0
- sxs/utilities/__init__.py +6 -2
- sxs/utilities/sxs_identifiers.py +19 -9
- sxs/utilities/url.py +4 -1
- sxs/waveforms/format_handlers/rotating_paired_diff_multishuffle_bzip2.py +9 -6
- sxs/waveforms/waveform_modes.py +7 -0
- sxs/zenodo/__init__.py +13 -0
- sxs/zenodo/api/deposit.py +2 -2
- {sxs-2023.3.1.dist-info → sxs-2024.0.0.dist-info}/METADATA +14 -15
- {sxs-2023.3.1.dist-info → sxs-2024.0.0.dist-info}/RECORD +21 -18
- {sxs-2023.3.1.dist-info → sxs-2024.0.0.dist-info}/WHEEL +0 -0
- {sxs-2023.3.1.dist-info → sxs-2024.0.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from warnings import warn
|
|
3
|
+
from .. import doi_url, Metadata
|
|
4
|
+
from ..utilities import (
|
|
5
|
+
sxs_id_and_version, lev_number, sxs_path_to_system_path,
|
|
6
|
+
download_file, sxs_directory, read_config
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def Simulation(location, *args, **kwargs):
|
|
11
|
+
"""Construct a Simulation object from a location string
|
|
12
|
+
|
|
13
|
+
The location string should be an SXS ID with an optional version
|
|
14
|
+
number, and possibly a Lev specification, as in
|
|
15
|
+
"SXS:BBH:1234v2.0/Lev5". The default version number is "v2.0",
|
|
16
|
+
and the default Lev number is the highest available. Note that
|
|
17
|
+
the version number must be in the form "v2.0", not "2.0", and it
|
|
18
|
+
must exist, or no files will be found to load the data from.
|
|
19
|
+
|
|
20
|
+
The returned object will be either a `Simulation_v1` or
|
|
21
|
+
`Simulation_v2` object, depending on the version number.
|
|
22
|
+
Hopefully, most details of the two versions will be hidden from
|
|
23
|
+
the user, so that the interface is identical.
|
|
24
|
+
|
|
25
|
+
Note that some simulations are deprecated and/or superseded by
|
|
26
|
+
other simulations. By default, this function will raise an error
|
|
27
|
+
if you try to load a deprecated or superseded simulation. There
|
|
28
|
+
are several ways to override this behavior:
|
|
29
|
+
|
|
30
|
+
1. Pass `ignore_deprecation=True` to completely bypass even
|
|
31
|
+
checking for deprecation or supersession. No warnings or
|
|
32
|
+
errors will be issued.
|
|
33
|
+
2. Include an explicit version number in the `location`
|
|
34
|
+
string, as in "SXS:BBH:0001v2.0". A warning will be issued
|
|
35
|
+
that the simulation is deprecated, but it will be loaded
|
|
36
|
+
anyway.
|
|
37
|
+
3. Pass `auto_supersede=True` to automatically load the
|
|
38
|
+
superseding simulation, if there is only one. Because no
|
|
39
|
+
superseding simulation can be *precisely* the same as the
|
|
40
|
+
deprecated one, there may be multiple superseding simulations
|
|
41
|
+
that have very similar parameters, in which case an error will
|
|
42
|
+
be raised and you must explicitly choose one. If there is
|
|
43
|
+
only one, a warning will be issued, but the superseding
|
|
44
|
+
simulation will be loaded.
|
|
45
|
+
|
|
46
|
+
Otherwise, a `ValueError` will be raised, with an explanation and
|
|
47
|
+
suggestions on what you might want to do.
|
|
48
|
+
|
|
49
|
+
Parameters
|
|
50
|
+
----------
|
|
51
|
+
location : str
|
|
52
|
+
The location string for the simulation. This must include the
|
|
53
|
+
SXS ID, but can also include the version and Lev number, as
|
|
54
|
+
described above.
|
|
55
|
+
|
|
56
|
+
Other parameters
|
|
57
|
+
----------------
|
|
58
|
+
ignore_deprecation : bool
|
|
59
|
+
If `True`, completely bypass checking for deprecation or
|
|
60
|
+
supersession. No warnings or errors will be issued.
|
|
61
|
+
auto_supersede : bool
|
|
62
|
+
If `True`, automatically load the superseding simulation, if
|
|
63
|
+
there is only one. If there are multiple superseding
|
|
64
|
+
simulations, an error will be raised, and you must explicitly
|
|
65
|
+
choose one. If there is only one, a warning will still be
|
|
66
|
+
issued, but the superseding simulation will be loaded. Note
|
|
67
|
+
that this can also be set in the configuration file with
|
|
68
|
+
`sxs.write_config(auto_supersede=True)`.
|
|
69
|
+
extrapolation : str
|
|
70
|
+
The extrapolation order to use for the strain and Psi4 data.
|
|
71
|
+
This is only relevant for versions 1 and 2 of the data format,
|
|
72
|
+
both of which default to "N2". Other options include "N3",
|
|
73
|
+
"N4", and "Outer". "Nx" refers to extrapolation by
|
|
74
|
+
polynomials in 1/r with degree `x`, while "Outer" refers to
|
|
75
|
+
data extracted at the outermost extraction radius but
|
|
76
|
+
corrected for time-dilation and areal-radius effects.
|
|
77
|
+
|
|
78
|
+
Returns
|
|
79
|
+
-------
|
|
80
|
+
simulation : SimulationBase
|
|
81
|
+
A `Simulation_v1` or `Simulation_v2` object, depending on the
|
|
82
|
+
version of the simulation data.
|
|
83
|
+
|
|
84
|
+
Note that all remaining arguments (including keyword arguments)
|
|
85
|
+
are passed on to the `SimulationBase`, `Simulation_v1`, and/or
|
|
86
|
+
`Simulation_v2` constructors, though none currently recognize any
|
|
87
|
+
arguments other than those listed above.
|
|
88
|
+
|
|
89
|
+
"""
|
|
90
|
+
from .. import load
|
|
91
|
+
|
|
92
|
+
# Extract the simulation ID, version, and Lev from the location string
|
|
93
|
+
simulation_id, input_version = sxs_id_and_version(location)
|
|
94
|
+
if not simulation_id:
|
|
95
|
+
raise ValueError(f"Invalid SXS ID in '{simulation_id}'")
|
|
96
|
+
input_lev_number = lev_number(location) # Will be `None` if not present
|
|
97
|
+
|
|
98
|
+
# Load the simulation catalog and check if simulation ID exists in the catalog
|
|
99
|
+
simulations = load("simulations")
|
|
100
|
+
if simulation_id not in simulations:
|
|
101
|
+
raise ValueError(f"Simulation '{simulation_id}' not found in simulation catalog")
|
|
102
|
+
|
|
103
|
+
# Attach metadata to this object
|
|
104
|
+
metadata = Metadata(simulations[simulation_id])
|
|
105
|
+
series = simulations.dataframe.loc[simulation_id]
|
|
106
|
+
|
|
107
|
+
# Check if the specified version exists in the simulation catalog
|
|
108
|
+
if input_version not in metadata.DOI_versions:
|
|
109
|
+
raise ValueError(f"Version '{input_version}' not found in simulation catalog for '{simulation_id}'")
|
|
110
|
+
|
|
111
|
+
# Set various pieces of information about the simulation
|
|
112
|
+
version = input_version or max(metadata.DOI_versions)
|
|
113
|
+
if not version.startswith("v"):
|
|
114
|
+
raise ValueError(f"Invalid version string '{version}'")
|
|
115
|
+
sxs_id_stem = simulation_id
|
|
116
|
+
sxs_id = f"{sxs_id_stem}{version}"
|
|
117
|
+
url = f"{doi_url}{sxs_id}"
|
|
118
|
+
|
|
119
|
+
# Deal with "superseded_by" field, or "deprecated" keyword in the metadata
|
|
120
|
+
if not kwargs.get("ignore_deprecation", False):
|
|
121
|
+
auto_supersede = kwargs.get("auto_supersede", read_config("auto_supersede", False))
|
|
122
|
+
if (
|
|
123
|
+
input_version
|
|
124
|
+
and not auto_supersede
|
|
125
|
+
and ("deprecated" in metadata.get("keywords", []) or metadata.get("superseded_by", False))
|
|
126
|
+
):
|
|
127
|
+
message = ("\n"
|
|
128
|
+
+ f"Simulation '{sxs_id_stem}' is deprecated and/or superseded.\n"
|
|
129
|
+
+ "Normally, this simulation should no longer be used, but you\n"
|
|
130
|
+
+ f"explicitly requested version '{input_version}', so it is being used.\n"
|
|
131
|
+
)
|
|
132
|
+
warn(message, DeprecationWarning)
|
|
133
|
+
else:
|
|
134
|
+
if "superseded_by" in metadata:
|
|
135
|
+
superseded_by = metadata["superseded_by"]
|
|
136
|
+
if auto_supersede and isinstance(superseded_by, list):
|
|
137
|
+
raise ValueError(
|
|
138
|
+
f"`auto_supersede` is enabled, but simulation '{sxs_id}' is\n"
|
|
139
|
+
+ "superseded by multiple simulations. You must choose one\n"
|
|
140
|
+
+ "explicitly from the list:\n"
|
|
141
|
+
+ "\n".join(f" {s}" for s in superseded_by)
|
|
142
|
+
+ "\nAlternatively, you could pass `ignore_deprecation=True` or\n"
|
|
143
|
+
+ "specify a version to load this waveform anyway."
|
|
144
|
+
)
|
|
145
|
+
elif auto_supersede and isinstance(superseded_by, str):
|
|
146
|
+
message = f"Simulation '{sxs_id}' is being automatically superseded by '{superseded_by}'."
|
|
147
|
+
warn(message, DeprecationWarning)
|
|
148
|
+
new_location = f"{superseded_by}{input_version}"
|
|
149
|
+
if input_lev_number:
|
|
150
|
+
new_location += f"/Lev{input_lev_number}"
|
|
151
|
+
return Simulation(new_location, *args, **kwargs)
|
|
152
|
+
elif isinstance(superseded_by, list):
|
|
153
|
+
raise ValueError(
|
|
154
|
+
f"Simulation '{sxs_id}' is superseded by multiple simulations.\n"
|
|
155
|
+
+ "Even if you enable `auto_supersede`, with multiple options, you\n"
|
|
156
|
+
+ "must choose one explicitly from the list:\n"
|
|
157
|
+
+ "\n".join(f" {s}" for s in superseded_by)
|
|
158
|
+
+ "\nAlternatively, you could pass `ignore_deprecation=True` or\n"
|
|
159
|
+
+ "specify a version to load this waveform anyway."
|
|
160
|
+
)
|
|
161
|
+
elif isinstance(superseded_by, str):
|
|
162
|
+
raise ValueError(
|
|
163
|
+
f"Simulation '{sxs_id}' is superseded by '{superseded_by}'.\n"
|
|
164
|
+
+ "Note that you could enable `auto_supersede` to automatically\n"
|
|
165
|
+
+ "load the superseding simulation. Alternatively, you could\n"
|
|
166
|
+
+ "pass `ignore_deprecation=True` or specify a version to load\n"
|
|
167
|
+
+ "this waveform anyway."
|
|
168
|
+
)
|
|
169
|
+
else:
|
|
170
|
+
raise ValueError(
|
|
171
|
+
f"Simulation '{sxs_id}' is superseded by '{superseded_by}'.\n"
|
|
172
|
+
+ "Note that you could pass `ignore_deprecation=True` or\n"
|
|
173
|
+
+ "specify a version to load this waveform anyway."
|
|
174
|
+
)
|
|
175
|
+
if "deprecated" in metadata.get("keywords", []):
|
|
176
|
+
raise ValueError(
|
|
177
|
+
f"Simulation '{sxs_id}' is deprecated but has no superseding simulation.\n"
|
|
178
|
+
+ "Note that you could pass `ignore_deprecation=True` or specify a version\n"
|
|
179
|
+
+ "to to load this waveform anyway."
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# We want to do this *after* deprecation checking, to avoid possibly unnecessary web requests
|
|
183
|
+
files = get_file_info(metadata, sxs_id)
|
|
184
|
+
|
|
185
|
+
# If Lev is given as part of `location`, use it; otherwise, use the highest available
|
|
186
|
+
lev_numbers = sorted({lev for f in files if (lev:=lev_number(f))})
|
|
187
|
+
output_lev_number = input_lev_number or max(lev_numbers)
|
|
188
|
+
location = f"{sxs_id_stem}{version}/Lev{output_lev_number}"
|
|
189
|
+
|
|
190
|
+
# Finally, figure out which version of the simulation to load and dispatch
|
|
191
|
+
version_number = float(version[1:])
|
|
192
|
+
if 1 <= version_number < 2.0:
|
|
193
|
+
return Simulation_v1(
|
|
194
|
+
metadata, series, version, sxs_id_stem, sxs_id, url, files, lev_numbers, output_lev_number, location, *args, **kwargs
|
|
195
|
+
)
|
|
196
|
+
elif 2 <= version_number < 3.0:
|
|
197
|
+
return Simulation_v2(
|
|
198
|
+
metadata, series, version, sxs_id_stem, sxs_id, url, files, lev_numbers, output_lev_number, location, *args, **kwargs
|
|
199
|
+
)
|
|
200
|
+
else:
|
|
201
|
+
raise ValueError(f"Version '{version}' not yet supported")
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class SimulationBase:
|
|
205
|
+
"""Base class for Simulation objects
|
|
206
|
+
|
|
207
|
+
Note that users almost certainly never need to call this function;
|
|
208
|
+
see the `Simulation` function or `sxs.load` function instead.
|
|
209
|
+
|
|
210
|
+
Attributes
|
|
211
|
+
----------
|
|
212
|
+
metadata : Metadata
|
|
213
|
+
Metadata object for the simulation
|
|
214
|
+
series : pandas.Series
|
|
215
|
+
The metadata, as extracted from the `simulations.dataframe`,
|
|
216
|
+
meaning that it has columns consistent with other simulations,
|
|
217
|
+
even when the underlying Metadata objects do not. Note that
|
|
218
|
+
`metadata` is an alias for this attribute, just based on the
|
|
219
|
+
use of that name for `simulations`, but technically `pandas`
|
|
220
|
+
distinguishes a single row like this as a `Series` object.
|
|
221
|
+
version : str
|
|
222
|
+
Version number of the simulation
|
|
223
|
+
sxs_id_stem : str
|
|
224
|
+
SXS ID without the version number or Lev
|
|
225
|
+
sxs_id : str
|
|
226
|
+
SXS ID with the version number
|
|
227
|
+
location : str
|
|
228
|
+
Location string for the simulation, including the SXS ID,
|
|
229
|
+
version number, and Lev number.
|
|
230
|
+
url : str
|
|
231
|
+
URL for the DOI of the simulation
|
|
232
|
+
files : dict
|
|
233
|
+
Dictionary of file information for the simulation. The keys
|
|
234
|
+
are of the form "Lev5:Horizons.h5", and the values are
|
|
235
|
+
dictionaries with keys "checksum", "size", and "link".
|
|
236
|
+
lev_numbers : list
|
|
237
|
+
List of available Lev numbers for the simulation.
|
|
238
|
+
lev_number : int
|
|
239
|
+
Chosen Lev number for the simulation.
|
|
240
|
+
horizons : Horizons
|
|
241
|
+
Horizons object for the simulation
|
|
242
|
+
strain : Waveform
|
|
243
|
+
Strain Waveform object for the simulation. Note that `h` is
|
|
244
|
+
an alias for this attribute, both of which are case
|
|
245
|
+
insensitive: `Strain` and `H` are also acceptable.
|
|
246
|
+
psi4 : Waveform
|
|
247
|
+
Psi4 Waveform object for the simulation. Note that this
|
|
248
|
+
attribute is also case insensitive: `Psi4` is also acceptable.
|
|
249
|
+
psi3 : Waveform
|
|
250
|
+
Psi3 Waveform object for the simulation. Note that this
|
|
251
|
+
attribute is also case insensitive: `Psi3` is also acceptable.
|
|
252
|
+
In versions 1 and 2, this attribute will raise an error
|
|
253
|
+
because the data is not available.
|
|
254
|
+
psi2 : Waveform
|
|
255
|
+
Psi2 Waveform object for the simulation. Note that this
|
|
256
|
+
attribute is also case insensitive: `Psi2` is also acceptable.
|
|
257
|
+
In versions 1 and 2, this attribute will raise an error
|
|
258
|
+
because the data is not available.
|
|
259
|
+
psi1 : Waveform
|
|
260
|
+
Psi1 Waveform object for the simulation. Note that this
|
|
261
|
+
attribute is also case insensitive: `Psi1` is also acceptable.
|
|
262
|
+
In versions 1 and 2, this attribute will raise an error
|
|
263
|
+
because the data is not available.
|
|
264
|
+
psi0 : Waveform
|
|
265
|
+
Psi0 Waveform object for the simulation. Note that this
|
|
266
|
+
attribute is also case insensitive: `Psi0` is also acceptable.
|
|
267
|
+
In versions 1 and 2, this attribute will raise an error
|
|
268
|
+
because the data is not available.
|
|
269
|
+
"""
|
|
270
|
+
def __init__(self,
|
|
271
|
+
metadata, series, version, sxs_id_stem, sxs_id, url, files, lev_numbers, lev_number, location,
|
|
272
|
+
*args, **kwargs
|
|
273
|
+
):
|
|
274
|
+
self.metadata = metadata
|
|
275
|
+
self.series = series
|
|
276
|
+
self.version = version
|
|
277
|
+
self.sxs_id_stem = sxs_id_stem
|
|
278
|
+
self.sxs_id = sxs_id
|
|
279
|
+
self.url = url
|
|
280
|
+
self.files = files
|
|
281
|
+
self.lev_numbers = lev_numbers
|
|
282
|
+
self.lev_number = lev_number
|
|
283
|
+
self.location = location
|
|
284
|
+
|
|
285
|
+
def __repr__(self):
|
|
286
|
+
return f"""{type(self).__qualname__}("{self.sxs_id}")"""
|
|
287
|
+
|
|
288
|
+
def __str__(self):
|
|
289
|
+
return repr(self)
|
|
290
|
+
|
|
291
|
+
@property
|
|
292
|
+
def dataframe(self):
|
|
293
|
+
return self.series
|
|
294
|
+
|
|
295
|
+
@property
|
|
296
|
+
def versions(self):
|
|
297
|
+
return self.metadata.DOI_versions
|
|
298
|
+
|
|
299
|
+
@property
|
|
300
|
+
def lev(self):
|
|
301
|
+
return f"Lev{self.lev_number}"
|
|
302
|
+
|
|
303
|
+
@property
|
|
304
|
+
def Lev(self):
|
|
305
|
+
return self.lev
|
|
306
|
+
|
|
307
|
+
def load_horizons(self):
|
|
308
|
+
from .. import load
|
|
309
|
+
sxs_id_path = Path(sxs_path_to_system_path(self.sxs_id))
|
|
310
|
+
horizons_path = self.horizons_path
|
|
311
|
+
horizons_location = self.files.get(horizons_path)["link"]
|
|
312
|
+
horizons_truepath = sxs_id_path / sxs_path_to_system_path(horizons_path)
|
|
313
|
+
return load(horizons_location, truepath=horizons_truepath)
|
|
314
|
+
|
|
315
|
+
@property
|
|
316
|
+
def horizons(self):
|
|
317
|
+
if not hasattr(self, "_horizons"):
|
|
318
|
+
self._horizons = self.load_horizons()
|
|
319
|
+
return self._horizons
|
|
320
|
+
|
|
321
|
+
@property
|
|
322
|
+
def strain(self):
|
|
323
|
+
if not hasattr(self, "_strain"):
|
|
324
|
+
self._strain = self.load_waveform(*self.strain_path)
|
|
325
|
+
return self._strain
|
|
326
|
+
Strain = strain
|
|
327
|
+
h = strain
|
|
328
|
+
H = strain
|
|
329
|
+
|
|
330
|
+
# I'm not entirely sure about the conjugations and factors of 2 in
|
|
331
|
+
# shear and news in our conventions. These will have to wait for
|
|
332
|
+
# later.
|
|
333
|
+
#
|
|
334
|
+
# @property
|
|
335
|
+
# def shear(self):
|
|
336
|
+
# if not hasattr(self, "_shear"):
|
|
337
|
+
# self._shear = self.strain.bar / 2
|
|
338
|
+
# return self._shear
|
|
339
|
+
# sigma = shear
|
|
340
|
+
# σ = shear
|
|
341
|
+
# Shear = shear
|
|
342
|
+
# Sigma = shear
|
|
343
|
+
# Σ = shear
|
|
344
|
+
#
|
|
345
|
+
# @property
|
|
346
|
+
# def news(self):
|
|
347
|
+
# if not hasattr(self, "_news"):
|
|
348
|
+
# self._news = self.strain.dot
|
|
349
|
+
# return self._news
|
|
350
|
+
# News = news
|
|
351
|
+
|
|
352
|
+
@property
|
|
353
|
+
def psi4(self):
|
|
354
|
+
if not hasattr(self, "_psi4"):
|
|
355
|
+
self._psi4 = self.load_waveform(*self.psi4_path)
|
|
356
|
+
return self._psi4
|
|
357
|
+
Psi4 = psi4
|
|
358
|
+
|
|
359
|
+
@property
|
|
360
|
+
def psi3(self):
|
|
361
|
+
raise AttributeError(f"Psi3 is not available for version {self.version} of the data")
|
|
362
|
+
Psi3 = psi3
|
|
363
|
+
|
|
364
|
+
@property
|
|
365
|
+
def psi2(self):
|
|
366
|
+
raise AttributeError(f"Psi2 is not available for version {self.version} of the data")
|
|
367
|
+
Psi2 = psi2
|
|
368
|
+
|
|
369
|
+
@property
|
|
370
|
+
def psi1(self):
|
|
371
|
+
raise AttributeError(f"Psi1 is not available for version {self.version} of the data")
|
|
372
|
+
Psi1 = psi1
|
|
373
|
+
|
|
374
|
+
@property
|
|
375
|
+
def psi0(self):
|
|
376
|
+
raise AttributeError(f"Psi0 is not available for version {self.version} of the data")
|
|
377
|
+
Psi0 = psi0
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
class Simulation_v1(SimulationBase):
|
|
381
|
+
"""Simulation object for version 1 of the data format
|
|
382
|
+
|
|
383
|
+
Note that users almost certainly never need to call this function;
|
|
384
|
+
see the `Simulation` function or `sxs.load` function instead. See
|
|
385
|
+
also `SimulationBase` for the base class that this class inherits
|
|
386
|
+
from.
|
|
387
|
+
"""
|
|
388
|
+
# We have to deal with the fact that some early file paths on
|
|
389
|
+
# Zenodo included the SXS ID as a prefix, while others did not.
|
|
390
|
+
# This means that we have to check for both possibilities in
|
|
391
|
+
# `load_horizons` and `load_waveform`.
|
|
392
|
+
|
|
393
|
+
def __init__(self, *args, **kwargs):
|
|
394
|
+
super().__init__(*args, **kwargs)
|
|
395
|
+
self.extrapolation = kwargs.get("extrapolation", "N2")
|
|
396
|
+
|
|
397
|
+
@property
|
|
398
|
+
def horizons_path(self):
|
|
399
|
+
return f"{self.lev}/Horizons.h5"
|
|
400
|
+
|
|
401
|
+
def load_horizons(self):
|
|
402
|
+
from .. import load
|
|
403
|
+
sxs_id_path = Path(sxs_path_to_system_path(self.sxs_id))
|
|
404
|
+
horizons_path = self.horizons_path
|
|
405
|
+
if horizons_path in self.files:
|
|
406
|
+
horizons_location = self.files.get(horizons_path)["link"]
|
|
407
|
+
else:
|
|
408
|
+
if (extended_horizons_path := f"{self.sxs_id_stem}/{horizons_path}") in self.files:
|
|
409
|
+
horizons_location = self.files.get(extended_horizons_path)["link"]
|
|
410
|
+
else:
|
|
411
|
+
raise ValueError(f"File '{horizons_path}' not found in simulation files")
|
|
412
|
+
horizons_truepath = sxs_id_path / sxs_path_to_system_path(horizons_path)
|
|
413
|
+
return load(horizons_location, truepath=horizons_truepath)
|
|
414
|
+
|
|
415
|
+
@property
|
|
416
|
+
def strain_path(self):
|
|
417
|
+
extrapolation = (
|
|
418
|
+
f"Extrapolated_{self.extrapolation}.dir"
|
|
419
|
+
if self.extrapolation != "Outer"
|
|
420
|
+
else "OutermostExtraction.dir"
|
|
421
|
+
)
|
|
422
|
+
return (
|
|
423
|
+
f"{self.lev}/rhOverM_Asymptotic_GeometricUnits_CoM.h5",
|
|
424
|
+
extrapolation
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
@property
|
|
428
|
+
def psi4_path(self):
|
|
429
|
+
extrapolation = (
|
|
430
|
+
f"Extrapolated_{self.extrapolation}.dir"
|
|
431
|
+
if self.extrapolation != "Outer"
|
|
432
|
+
else "OutermostExtraction.dir"
|
|
433
|
+
)
|
|
434
|
+
return (
|
|
435
|
+
f"{self.lev}/rMPsi4_Asymptotic_GeometricUnits_CoM.h5",
|
|
436
|
+
extrapolation
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
def load_waveform(self, file_name, group):
|
|
440
|
+
from .. import load
|
|
441
|
+
if file_name in self.files:
|
|
442
|
+
location = self.files.get(file_name)["link"]
|
|
443
|
+
else:
|
|
444
|
+
if (extended_file_name := f"{self.sxs_id_stem}/{file_name}") in self.files:
|
|
445
|
+
location = self.files.get(extended_file_name)["link"]
|
|
446
|
+
else:
|
|
447
|
+
raise ValueError(f"File '{file_name}' not found in simulation files")
|
|
448
|
+
sxs_id_path = Path(sxs_path_to_system_path(self.sxs_id))
|
|
449
|
+
truepath = sxs_id_path / sxs_path_to_system_path(file_name)
|
|
450
|
+
w = load(location, truepath=truepath, extrapolation_order=group)
|
|
451
|
+
w.metadata = self.metadata
|
|
452
|
+
return w
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
class Simulation_v2(SimulationBase):
|
|
456
|
+
"""Simulation object for version 2 of the data format
|
|
457
|
+
|
|
458
|
+
Note that users almost certainly never need to call this function;
|
|
459
|
+
see the `Simulation` function or `sxs.load` function instead. See
|
|
460
|
+
also `SimulationBase` for the base class that this class inherits
|
|
461
|
+
from.
|
|
462
|
+
"""
|
|
463
|
+
def __init__(self, *args, **kwargs):
|
|
464
|
+
super().__init__(*args, **kwargs)
|
|
465
|
+
self.extrapolation = kwargs.get("extrapolation", "N2")
|
|
466
|
+
|
|
467
|
+
@property
|
|
468
|
+
def horizons_path(self):
|
|
469
|
+
return f"{self.lev}:Horizons.h5"
|
|
470
|
+
|
|
471
|
+
@property
|
|
472
|
+
def strain_path(self):
|
|
473
|
+
return (
|
|
474
|
+
f"{self.lev}:Strain_{self.extrapolation}",
|
|
475
|
+
"/"
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
@property
|
|
479
|
+
def psi4_path(self):
|
|
480
|
+
extrapolation = (
|
|
481
|
+
f"Extrapolated_{self.extrapolation}.dir"
|
|
482
|
+
if self.extrapolation != "Outer"
|
|
483
|
+
else "OutermostExtraction.dir"
|
|
484
|
+
)
|
|
485
|
+
return (
|
|
486
|
+
f"{self.lev}:ExtraWaveforms",
|
|
487
|
+
f"/rMPsi4_Asymptotic_GeometricUnits_CoM_Mem/{extrapolation}"
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
def load_waveform(self, file_name, group):
|
|
491
|
+
from .. import load
|
|
492
|
+
# Note that `name` should not have the file ending on input,
|
|
493
|
+
# but we will strip it regardless with `.stem`.
|
|
494
|
+
file_name = Path(file_name).stem
|
|
495
|
+
sxs_id_path = Path(sxs_path_to_system_path(self.sxs_id))
|
|
496
|
+
h5_path = f"{file_name}.h5"
|
|
497
|
+
json_path = f"{file_name}.json"
|
|
498
|
+
h5_location = self.files.get(h5_path)["link"]
|
|
499
|
+
json_location = self.files.get(json_path)["link"]
|
|
500
|
+
h5_truepath = sxs_id_path / sxs_path_to_system_path(h5_path)
|
|
501
|
+
json_truepath = sxs_id_path / sxs_path_to_system_path(json_path)
|
|
502
|
+
if not json_truepath.exists():
|
|
503
|
+
download_file(json_location, sxs_directory("cache") / json_truepath)
|
|
504
|
+
return load(h5_location, truepath=h5_truepath, group=group, metadata=self.metadata)
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
def get_file_info(metadata, sxs_id):
|
|
508
|
+
from .. import load_via_sxs_id
|
|
509
|
+
if "files" in metadata:
|
|
510
|
+
return metadata["files"]
|
|
511
|
+
truepath = Path(sxs_path_to_system_path(sxs_id)) / "zenodo_metadata.json"
|
|
512
|
+
record = load_via_sxs_id(sxs_id, "export/json", truepath=truepath)
|
|
513
|
+
entries = record["files"]["entries"]
|
|
514
|
+
return {
|
|
515
|
+
str(filename): {
|
|
516
|
+
"checksum": entry["checksum"],
|
|
517
|
+
"size": entry["size"],
|
|
518
|
+
"link": entry["links"]["content"],
|
|
519
|
+
}
|
|
520
|
+
for filename, entry in entries.items()
|
|
521
|
+
}
|