sxs 2025.0.5__tar.gz → 2025.0.7__tar.gz

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 (134) hide show
  1. {sxs-2025.0.5 → sxs-2025.0.7}/.github/workflows/build.yml +5 -0
  2. {sxs-2025.0.5 → sxs-2025.0.7}/CITATION.cff +2 -2
  3. {sxs-2025.0.5 → sxs-2025.0.7}/PKG-INFO +2 -2
  4. {sxs-2025.0.5 → sxs-2025.0.7}/pyproject.toml +1 -1
  5. sxs-2025.0.7/sxs/__version__.py +1 -0
  6. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/simulations/simulation.py +66 -52
  7. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/utilities/inspire.py +28 -23
  8. {sxs-2025.0.5 → sxs-2025.0.7}/tests/conftest.py +16 -6
  9. {sxs-2025.0.5 → sxs-2025.0.7}/tests/test_horizons.py +1 -1
  10. sxs-2025.0.7/tests/test_simulation.py +236 -0
  11. {sxs-2025.0.5 → sxs-2025.0.7}/tests/test_waveform_rotations.py +1 -1
  12. sxs-2025.0.5/sxs/__version__.py +0 -1
  13. sxs-2025.0.5/tests/test_simulation.py +0 -135
  14. {sxs-2025.0.5 → sxs-2025.0.7}/.codecov.yml +0 -0
  15. {sxs-2025.0.5 → sxs-2025.0.7}/.github/dependabot.yml +0 -0
  16. {sxs-2025.0.5 → sxs-2025.0.7}/.github/scripts/parse_bump_rule.py +0 -0
  17. {sxs-2025.0.5 → sxs-2025.0.7}/.github/workflows/pr_rtd_link.yml +0 -0
  18. {sxs-2025.0.5 → sxs-2025.0.7}/.gitignore +0 -0
  19. {sxs-2025.0.5 → sxs-2025.0.7}/.readthedocs.yaml +0 -0
  20. {sxs-2025.0.5 → sxs-2025.0.7}/LICENSE +0 -0
  21. {sxs-2025.0.5 → sxs-2025.0.7}/README.md +0 -0
  22. {sxs-2025.0.5 → sxs-2025.0.7}/docs/api/catalog.md +0 -0
  23. {sxs-2025.0.5 → sxs-2025.0.7}/docs/api/horizons.md +0 -0
  24. {sxs-2025.0.5 → sxs-2025.0.7}/docs/api/load.md +0 -0
  25. {sxs-2025.0.5 → sxs-2025.0.7}/docs/api/metadata.md +0 -0
  26. {sxs-2025.0.5 → sxs-2025.0.7}/docs/api/simulation.md +0 -0
  27. {sxs-2025.0.5 → sxs-2025.0.7}/docs/api/simulations.md +0 -0
  28. {sxs-2025.0.5 → sxs-2025.0.7}/docs/api/time_series.md +0 -0
  29. {sxs-2025.0.5 → sxs-2025.0.7}/docs/api/waveforms.md +0 -0
  30. {sxs-2025.0.5 → sxs-2025.0.7}/docs/html/main.html +0 -0
  31. {sxs-2025.0.5 → sxs-2025.0.7}/docs/images/favicon.ico +0 -0
  32. {sxs-2025.0.5 → sxs-2025.0.7}/docs/index.md +0 -0
  33. {sxs-2025.0.5 → sxs-2025.0.7}/docs/javascript/mathjax.js +0 -0
  34. {sxs-2025.0.5 → sxs-2025.0.7}/docs/julia.md +0 -0
  35. {sxs-2025.0.5 → sxs-2025.0.7}/docs/mathematica.md +0 -0
  36. {sxs-2025.0.5 → sxs-2025.0.7}/docs/stylesheets/extra.css +0 -0
  37. {sxs-2025.0.5 → sxs-2025.0.7}/docs/tutorials/00-Introduction.ipynb +0 -0
  38. {sxs-2025.0.5 → sxs-2025.0.7}/docs/tutorials/01-Simulations_and_Metadata.ipynb +0 -0
  39. {sxs-2025.0.5 → sxs-2025.0.7}/docs/tutorials/02-Simulation.ipynb +0 -0
  40. {sxs-2025.0.5 → sxs-2025.0.7}/docs/tutorials/03-Horizons.ipynb +0 -0
  41. {sxs-2025.0.5 → sxs-2025.0.7}/docs/tutorials/04-Waveforms.ipynb +0 -0
  42. {sxs-2025.0.5 → sxs-2025.0.7}/docs/tutorials/05-PreprocessingForFFTs.ipynb +0 -0
  43. {sxs-2025.0.5 → sxs-2025.0.7}/mkdocs.yml +0 -0
  44. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/__init__.py +0 -0
  45. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/catalog/__init__.py +0 -0
  46. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/catalog/catalog.py +0 -0
  47. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/catalog/create.py +0 -0
  48. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/catalog/description.py +0 -0
  49. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/handlers.py +0 -0
  50. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/horizons/__init__.py +0 -0
  51. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/horizons/spec_horizons_h5.py +0 -0
  52. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/horizons/xor_multishuffle_bzip2.py +0 -0
  53. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/julia/GWFrames.py +0 -0
  54. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/julia/__init__.py +0 -0
  55. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/juliapkg.json +0 -0
  56. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/metadata/__init__.py +0 -0
  57. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/metadata/metadata.py +0 -0
  58. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/metadata/metric.py +0 -0
  59. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/simulations/__init__.py +0 -0
  60. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/simulations/analyze.py +0 -0
  61. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/simulations/local.py +0 -0
  62. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/simulations/simulations.py +0 -0
  63. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/time_series.py +0 -0
  64. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/utilities/__init__.py +0 -0
  65. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/utilities/bitwise.py +0 -0
  66. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/utilities/decimation/__init__.py +0 -0
  67. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/utilities/decimation/greedy_spline.py +0 -0
  68. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/utilities/decimation/linear_bisection.py +0 -0
  69. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/utilities/decimation/peak_greed.py +0 -0
  70. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/utilities/decimation/suppression.py +0 -0
  71. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/utilities/dicts.py +0 -0
  72. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/utilities/downloads.py +0 -0
  73. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/utilities/files.py +0 -0
  74. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/utilities/formats.py +0 -0
  75. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/utilities/lvcnr/__init__.py +0 -0
  76. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/utilities/lvcnr/comparisons.py +0 -0
  77. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/utilities/lvcnr/conversion.py +0 -0
  78. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/utilities/lvcnr/dataset.py +0 -0
  79. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/utilities/lvcnr/horizons.py +0 -0
  80. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/utilities/lvcnr/metadata.py +0 -0
  81. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/utilities/lvcnr/waveform_amp_phase.py +0 -0
  82. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/utilities/lvcnr/waveforms.py +0 -0
  83. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/utilities/monotonicity.py +0 -0
  84. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/utilities/pretty_print.py +0 -0
  85. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/utilities/references/__init__.py +0 -0
  86. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/utilities/references/ads.py +0 -0
  87. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/utilities/references/arxiv.py +0 -0
  88. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/utilities/references/fairchild_report.py +0 -0
  89. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/utilities/references/inspire.py +0 -0
  90. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/utilities/references/journal_abbreviations.py +0 -0
  91. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/utilities/references/references.py +0 -0
  92. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/utilities/select.py +0 -0
  93. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/utilities/smooth_functions.py +0 -0
  94. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/utilities/string_converters.py +0 -0
  95. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/utilities/sxs_directories.py +0 -0
  96. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/utilities/sxs_identifiers.py +0 -0
  97. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/utilities/url.py +0 -0
  98. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/waveforms/__init__.py +0 -0
  99. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/waveforms/alignment.py +0 -0
  100. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/waveforms/format_handlers/__init__.py +0 -0
  101. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/waveforms/format_handlers/grathena.py +0 -0
  102. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/waveforms/format_handlers/lvc.py +0 -0
  103. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/waveforms/format_handlers/nrar.py +0 -0
  104. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/waveforms/format_handlers/rotating_paired_diff_multishuffle_bzip2.py +0 -0
  105. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/waveforms/format_handlers/rotating_paired_xor_multishuffle_bzip2.py +0 -0
  106. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/waveforms/format_handlers/spectre_cce_v1.py +0 -0
  107. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/waveforms/memory.py +0 -0
  108. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/waveforms/mode_utilities.py +0 -0
  109. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/waveforms/norms.py +0 -0
  110. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/waveforms/transformations.py +0 -0
  111. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/waveforms/waveform_grid.py +0 -0
  112. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/waveforms/waveform_mixin.py +0 -0
  113. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/waveforms/waveform_modes.py +0 -0
  114. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/waveforms/waveform_mts.py +0 -0
  115. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/waveforms/waveform_signal.py +0 -0
  116. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/zenodo/__init__.py +0 -0
  117. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/zenodo/api/__init__.py +0 -0
  118. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/zenodo/api/deposit.py +0 -0
  119. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/zenodo/api/login.py +0 -0
  120. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/zenodo/api/records.py +0 -0
  121. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/zenodo/catalog.py +0 -0
  122. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/zenodo/creators.py +0 -0
  123. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/zenodo/simannex.py +0 -0
  124. {sxs-2025.0.5 → sxs-2025.0.7}/sxs/zenodo/surrogatemodeling.py +0 -0
  125. {sxs-2025.0.5 → sxs-2025.0.7}/tests/__init__.py +0 -0
  126. {sxs-2025.0.5 → sxs-2025.0.7}/tests/test_alignment.py +0 -0
  127. {sxs-2025.0.5 → sxs-2025.0.7}/tests/test_catalog.py +0 -0
  128. {sxs-2025.0.5 → sxs-2025.0.7}/tests/test_julia.py +0 -0
  129. {sxs-2025.0.5 → sxs-2025.0.7}/tests/test_loader.py +0 -0
  130. {sxs-2025.0.5 → sxs-2025.0.7}/tests/test_metadata.py +0 -0
  131. {sxs-2025.0.5 → sxs-2025.0.7}/tests/test_time_series.py +0 -0
  132. {sxs-2025.0.5 → sxs-2025.0.7}/tests/test_transformations.py +0 -0
  133. {sxs-2025.0.5 → sxs-2025.0.7}/tests/test_utilities.py +0 -0
  134. {sxs-2025.0.5 → sxs-2025.0.7}/tests/test_waveforms.py +0 -0
@@ -34,6 +34,11 @@ jobs:
34
34
  exclude:
35
35
  - os: windows-latest
36
36
  python-version: '3.13'
37
+ - os: macos-latest
38
+ python-version: '3.13'
39
+ include:
40
+ - os: macos-latest
41
+ python-version: '3.12'
37
42
 
38
43
  steps:
39
44
  - name: Skip replicates unless on main branch or using ubuntu-latest
@@ -10,5 +10,5 @@ authors:
10
10
  title: "The sxs package"
11
11
  license: MIT
12
12
  doi: 10.5281/zenodo.4034006
13
- version: 2025.0.5
14
- date-released: 2025-04-11
13
+ version: 2025.0.7
14
+ date-released: 2025-04-15
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sxs
3
- Version: 2025.0.5
3
+ Version: 2025.0.7
4
4
  Summary: Interface to data produced by the Simulating eXtreme Spacetimes collaboration
5
5
  Project-URL: Homepage, https://github.com/sxs-collaboration/sxs
6
6
  Project-URL: Documentation, https://sxs.readthedocs.io/
@@ -47,7 +47,7 @@ Requires-Dist: quaternionic>=1.0.15
47
47
  Requires-Dist: requests>=2.24.0
48
48
  Requires-Dist: scipy>=1.13
49
49
  Requires-Dist: spherical>=1.0.15
50
- Requires-Dist: sxscatalog>=3.0.12
50
+ Requires-Dist: sxscatalog>=3.0.13
51
51
  Requires-Dist: tqdm>=4.63.1
52
52
  Requires-Dist: urllib3>=1.25.10
53
53
  Provides-Extra: docs
@@ -18,7 +18,7 @@ classifiers = [
18
18
  "Topic :: Scientific/Engineering :: Astronomy"
19
19
  ]
20
20
  dependencies = [
21
- "sxscatalog >=3.0.12",
21
+ "sxscatalog >=3.0.13",
22
22
  "numpy >=1.25",
23
23
  "scipy >=1.13",
24
24
  "numba >=0.55; implementation_name == 'cpython'",
@@ -0,0 +1 @@
1
+ __version__ = "2025.0.7"
@@ -7,6 +7,32 @@ from ..utilities import (
7
7
  )
8
8
 
9
9
 
10
+ def search_prefixes(file, lev, files, ending=""):
11
+ """Find the actual filename present in the list of files
12
+
13
+ Different versions of Zenodo and CaltechDATA place different
14
+ restrictions on filenames — and specifically, things I would
15
+ consider to be directories. If there are multiple Levs for a
16
+ simulation, we have to somehow specify the Lev in the filename.
17
+ The permitted strings for doing so have changed over the years, so
18
+ the directory separator was allowed to be a "/" for old systems,
19
+ but must be something else now; we settled on ":". However, if
20
+ there is just one Lev in a simulation, and we try to save the
21
+ files in a consistent way — including the Lev with the appropriate
22
+ separator, different versions of Zenodo and CaltechDATA will
23
+ either allow or silently remove that prefix. The simplest
24
+ approach is to just search over all possibilities, for which
25
+ filename is actually present in the data. That's what this
26
+ function does.
27
+
28
+ """
29
+ for prefix in [f"{lev}:", f"{lev}/", ""]:
30
+ fn = f"{prefix}{file}"
31
+ if f"{fn}{ending}" in files:
32
+ return fn
33
+ raise ValueError(f"{file}{ending} not found in any form in files")
34
+
35
+
10
36
  def Simulation(location, *args, **kwargs):
11
37
  """Construct a Simulation object from a location string
12
38
 
@@ -113,8 +139,8 @@ def Simulation(location, *args, **kwargs):
113
139
  # Extract the simulation ID, version, and Lev from the location string
114
140
  simulation_id, input_version = sxs_id_and_version(location)
115
141
  if not simulation_id:
116
- if location.split("/Lev")[0] in simulations:
117
- simulation_id = location.split("/Lev")[0]
142
+ if (sim_id := location.split("/Lev")[0]) in simulations:
143
+ simulation_id = sim_id
118
144
  input_version = latest_version
119
145
  else:
120
146
  raise ValueError(f"Invalid SXS ID in '{simulation_id}'")
@@ -129,7 +155,11 @@ def Simulation(location, *args, **kwargs):
129
155
  series = simulations.dataframe.loc[simulation_id]
130
156
 
131
157
  # If input_version is not the default, remove "files" from metadata
132
- if input_version and input_version != max(metadata.get("DOI_versions", []), default=""):
158
+ version_is_not_default = (
159
+ input_version
160
+ and input_version != max(metadata.get("DOI_versions", []), default="")
161
+ )
162
+ if version_is_not_default:
133
163
  metadata = type(metadata)({
134
164
  key: value for key, value in metadata.items() if key != "files"
135
165
  })
@@ -211,26 +241,35 @@ def Simulation(location, *args, **kwargs):
211
241
  kwargs["deprecated"] = deprecated
212
242
 
213
243
  # We want to do this *after* deprecation checking, to avoid possibly unnecessary web requests
214
- if 1 <= float(version[1:]) < 3.0 and "files" in metadata:
215
- # The simulation metadata is points to files with a different version
216
- del metadata["files"]
244
+ if version_is_not_default:
245
+ # The default metadata points to files with a different version, so delete this info
246
+ if "files" in metadata:
247
+ del metadata["files"]
217
248
  files = get_file_info(metadata, sxs_id, download=kwargs.get("download_file_info", None))
218
249
 
219
250
  # If Lev is given as part of `location`, use it; otherwise, use the highest available
220
- lev_numbers = sorted({lev for f in files if (lev:=lev_number(f))})
221
- if input_lev_number is not None and lev_numbers:
222
- if input_lev_number not in lev_numbers:
223
- raise ValueError(
224
- f"Lev number '{input_lev_number}' not found in simulation files for {sxs_id}"
225
- )
226
- max_lev_number = max(lev_numbers, default=np.nan)
251
+ lev_numbers = metadata.get(
252
+ "lev_numbers",
253
+ sorted({lev_num for f in metadata.get("files", []) if (lev_num:=lev_number(f))})
254
+ )
255
+ if not lev_numbers:
256
+ raise ValueError(f"Could not find Levs for {location}")
257
+ if input_lev_number is not None and input_lev_number not in lev_numbers:
258
+ raise ValueError(
259
+ f"Lev number '{input_lev_number}' not found in simulation files for {sxs_id}"
260
+ )
261
+ max_lev_number = max(lev_numbers)
227
262
  output_lev_number = input_lev_number or max_lev_number
263
+ if output_lev_number is None:
264
+ raise ValueError(
265
+ f"No Lev number found for {location}"
266
+ )
228
267
  location = f"{sxs_id_stem}{version}/Lev{output_lev_number}"
229
268
 
230
269
  # Keep the metadata around unless we're asking for an old version
231
270
  # or a less-than-maximal Lev
232
271
  if (
233
- version != latest_version
272
+ version_is_not_default
234
273
  or (lev_numbers and output_lev_number != max_lev_number)
235
274
  ):
236
275
  metadata = None
@@ -462,9 +501,9 @@ class SimulationBase:
462
501
  def metadata_path(self):
463
502
  for separator in [":", "/"]:
464
503
  for ending in [".json", ".txt"]:
465
- prefix = f"{self.lev}{separator}" if self.lev else ""
466
- if (fn := f"{prefix}metadata{ending}") in self.files:
467
- return fn
504
+ for prefix in ["", f"{self.lev}{separator}" if self.lev else ""]:
505
+ if (fn := f"{prefix}metadata{ending}") in self.files:
506
+ return fn
468
507
  raise ValueError(
469
508
  f"Metadata file not found in simulation files for {self.location}"
470
509
  )
@@ -758,50 +797,29 @@ class Simulation_v1(SimulationBase):
758
797
 
759
798
  @property
760
799
  def horizons_path(self):
761
- prefix = f"{self.lev}/" if self.lev else ""
762
- return f"{prefix}Horizons.h5"
763
-
764
- def load_horizons(self):
765
- from .. import load
766
- sxs_id_path = Path(self.sxs_id)
767
- horizons_path = self.horizons_path
768
- if horizons_path in self.files:
769
- horizons_location = self.files.get(horizons_path)["link"]
770
- else:
771
- # Some simulations used the SXS ID as a prefix in file paths
772
- # within the Zenodo upload in version 1.x of the catalog.
773
- if (extended_horizons_path := f"{self.sxs_id_stem}/{horizons_path}") in self.files:
774
- horizons_location = self.files.get(extended_horizons_path)["link"]
775
- else:
776
- raise ValueError(
777
- f"File '{horizons_path}' not found in simulation files for {self.location}"
778
- )
779
- horizons_truepath = Path(sxs_path_to_system_path(sxs_id_path / horizons_path))
780
- return load(horizons_location, truepath=horizons_truepath)
800
+ return search_prefixes("Horizons.h5", self.lev, self.files)
781
801
 
782
802
  @property
783
803
  def strain_path(self):
784
- prefix = f"{self.lev}/" if self.lev else ""
785
804
  extrapolation = (
786
805
  f"Extrapolated_{self.extrapolation}.dir"
787
806
  if self.extrapolation != "Outer"
788
807
  else "OutermostExtraction.dir"
789
808
  )
790
809
  return (
791
- f"{prefix}rhOverM_Asymptotic_GeometricUnits_CoM.h5",
810
+ search_prefixes("rhOverM_Asymptotic_GeometricUnits_CoM.h5", self.lev, self.files),
792
811
  extrapolation
793
812
  )
794
813
 
795
814
  @property
796
815
  def psi4_path(self):
797
- prefix = f"{self.lev}/" if self.lev else ""
798
816
  extrapolation = (
799
817
  f"Extrapolated_{self.extrapolation}.dir"
800
818
  if self.extrapolation != "Outer"
801
819
  else "OutermostExtraction.dir"
802
820
  )
803
821
  return (
804
- f"{prefix}rMPsi4_Asymptotic_GeometricUnits_CoM.h5",
822
+ search_prefixes("rMPsi4_Asymptotic_GeometricUnits_CoM.h5", self.lev, self.files),
805
823
  extrapolation
806
824
  )
807
825
 
@@ -848,14 +866,12 @@ class Simulation_v2(SimulationBase):
848
866
 
849
867
  @property
850
868
  def horizons_path(self):
851
- prefix = f"{self.lev}:" if self.lev else ""
852
- return f"{prefix}Horizons.h5"
869
+ return search_prefixes("Horizons.h5", self.lev, self.files)
853
870
 
854
871
  @property
855
872
  def strain_path(self):
856
- prefix = f"{self.lev}:" if self.lev else ""
857
873
  return (
858
- f"{prefix}Strain_{self.extrapolation}",
874
+ search_prefixes(f"Strain_{self.extrapolation}", self.lev, self.files, ".h5"),
859
875
  "/"
860
876
  )
861
877
 
@@ -866,9 +882,9 @@ class Simulation_v2(SimulationBase):
866
882
  if self.extrapolation != "Outer"
867
883
  else "OutermostExtraction.dir"
868
884
  )
869
- prefix = f"{self.lev}:" if self.lev else ""
885
+ prefix = f"{self.lev}:" if len(self.lev_numbers)>1 else ""
870
886
  return (
871
- f"{prefix}ExtraWaveforms",
887
+ search_prefixes(f"ExtraWaveforms", self.lev, self.files, ".h5"),
872
888
  f"/rMPsi4_Asymptotic_GeometricUnits_CoM_Mem/{extrapolation}"
873
889
  )
874
890
 
@@ -905,20 +921,18 @@ class Simulation_v3(Simulation_v2):
905
921
 
906
922
  @property
907
923
  def strain_path(self):
908
- prefix = f"{self.lev}:" if self.lev else ""
909
924
  return (
910
- f"{prefix}Strain_{self.extrapolation}",
925
+ search_prefixes(f"Strain_{self.extrapolation}", self.lev, self.files, ".h5"),
911
926
  "/"
912
927
  ) if self.extrapolation == self.default_extrapolation else (
913
- f"{prefix}ExtraWaveforms",
928
+ search_prefixes("ExtraWaveforms", self.lev, self.files, ".h5"),
914
929
  f"/Strain_{self.extrapolation}.dir"
915
930
  )
916
931
 
917
932
  @property
918
933
  def psi4_path(self):
919
- prefix = f"{self.lev}:" if self.lev else ""
920
934
  return (
921
- f"{prefix}ExtraWaveforms",
935
+ search_prefixes("ExtraWaveforms", self.lev, self.files, ".h5"),
922
936
  f"/Psi4_{self.extrapolation}.dir"
923
937
  )
924
938
 
@@ -44,7 +44,7 @@ def inspire2doi(inspire_bibtex_key, raise_exceptions=False):
44
44
 
45
45
 
46
46
  def query(
47
- query,
47
+ query_string,
48
48
  sort="mostrecent",
49
49
  page=1,
50
50
  size=1000,
@@ -56,7 +56,7 @@ def query(
56
56
 
57
57
  Parameters
58
58
  ----------
59
- query : str
59
+ query_string : str
60
60
  The search query. For details, see
61
61
  https://github.com/inspirehep/rest-api-doc/tree/master#search-query
62
62
  fields : str, optional
@@ -93,32 +93,37 @@ def query(
93
93
 
94
94
  """
95
95
  import sys
96
- import requests
97
- from requests.adapters import HTTPAdapter
98
- from urllib3.util.retry import Retry
99
96
 
100
- session = requests.Session()
97
+ # Reuse the same session, in case user repeatedly calls query
98
+ if not hasattr(query, "session"):
99
+ import requests
100
+ from requests.adapters import HTTPAdapter
101
+ from urllib3.util.retry import Retry
102
+
103
+ query.session = requests.Session()
104
+
105
+ ## Retry automatically on certain types of errors
106
+ retry = Retry(
107
+ total=10,
108
+ backoff_factor=0.1,
109
+ status_forcelist=[
110
+ 413, # Request Entity Too Large
111
+ 429, # Too Many Requests
112
+ 500, # Internal Server Error
113
+ 502, # Bad Gateway
114
+ 503, # Service Unavailable
115
+ 504, # Gateway Timeout
116
+ ],
117
+ )
118
+ adapter = HTTPAdapter(max_retries=retry)
119
+ query.session.mount("https://", adapter)
120
+
121
+ session = query.session
101
122
  collected_results = []
102
123
 
103
- ## Retry automatically on certain types of errors
104
- retry = Retry(
105
- total=10,
106
- backoff_factor=0.1,
107
- status_forcelist=[
108
- 413, # Request Entity Too Large
109
- 429, # Too Many Requests
110
- 500, # Internal Server Error
111
- 502, # Bad Gateway
112
- 503, # Service Unavailable
113
- 504, # Gateway Timeout
114
- ],
115
- )
116
- adapter = HTTPAdapter(max_retries=retry)
117
- session.mount("https://", adapter)
118
-
119
124
  url = api_url.format(record_type=record_type)
120
125
  params = {
121
- "q": query,
126
+ "q": query_string,
122
127
  "sort": sort,
123
128
  "size": size,
124
129
  "page": page,
@@ -44,19 +44,29 @@ def pytest_addoption(parser):
44
44
  help="Maximum ell value to test with slow tests")
45
45
  parser.addoption("--run_slow_tests", action="store_true", default=False,
46
46
  help="Run all tests, including slow ones")
47
+ parser.addoption("--run_many_downloads", action="store_true", default=False,
48
+ help="Run tests marked with @many_downloads")
47
49
 
48
50
 
49
51
  def pytest_configure(config):
50
52
  config.addinivalue_line("markers", "slow: marks tests as slow")
51
53
 
52
54
 
55
+ def pytest_configure(config):
56
+ config.addinivalue_line("markers", "many_downloads: marks tests that require many downloads")
57
+
58
+
53
59
  def pytest_collection_modifyitems(config, items):
54
- if config.getoption("--run_slow_tests"):
55
- return
56
- skip_slow = pytest.mark.skip(reason="need --run_slow_tests option to run")
57
- for item in items:
58
- if "slow" in item.keywords:
59
- item.add_marker(skip_slow)
60
+ if not config.getoption("--run_slow_tests"):
61
+ skip_slow = pytest.mark.skip(reason="need --run_slow_tests option to run")
62
+ for item in items:
63
+ if "slow" in item.keywords:
64
+ item.add_marker(skip_slow)
65
+ if not config.getoption("--run_many_downloads"):
66
+ skip_many_downloads = pytest.mark.skip(reason="need --run_many_downloads option to run")
67
+ for item in items:
68
+ if "many_downloads" in item.keywords:
69
+ item.add_marker(skip_many_downloads)
60
70
 
61
71
 
62
72
  def pytest_runtest_setup(item):
@@ -68,5 +68,5 @@ def test_horizon_existence():
68
68
  assert bhns_horizon.B is None
69
69
  assert bhns_horizon.C is None
70
70
  nsns = sxs.load("SXS:NSNS:0001", auto_supersede=True)
71
- with pytest.raises(ValueError, match="File '.*?' not found in simulation files for"):
71
+ with pytest.raises(ValueError, match="Horizons.h5 not found in any form in files"):
72
72
  nsns_horizon = nsns.horizons
@@ -0,0 +1,236 @@
1
+ import pytest
2
+ import sxs
3
+ from .conftest import skip_macOS_GH_actions_downloads
4
+
5
+ @pytest.mark.xfail(reason="Missing files in the catalog should be updated soon")
6
+ def test_catalog_file_sizes():
7
+ simulations = sxs.load("simulations")
8
+ success = True
9
+ missing_files = ""
10
+ for sxs_id, metadata in simulations.items():
11
+ for filename, fileinfo in metadata.get("files", {}).items():
12
+ if fileinfo.get("size", 0) < 500:
13
+ missing_files += f"{sxs_id} {filename}\n"
14
+ success = False
15
+ if not success:
16
+ print("\nThe following files are missing or suspiciously small:")
17
+ print(missing_files)
18
+ assert success
19
+
20
+
21
+ @skip_macOS_GH_actions_downloads
22
+ def test_sxs_load_v2():
23
+ # We'll use SXS:BBH:0001, even though it's deprecated, because we
24
+ # want to be sure that it always exists, and is accessible as a
25
+ # v2.0 record.
26
+ with pytest.warns(UserWarning):
27
+ s = sxs.load("SXS:BBH:0001v2.0")
28
+ assert s.sxs_id_stem == "SXS:BBH:0001"
29
+ assert s.version == "v2.0"
30
+ s.h
31
+ s.strain
32
+ s.H
33
+ s.Strain
34
+ s.horizons
35
+ s.Horizons
36
+ s.psi4
37
+ s.Psi4
38
+
39
+
40
+ @skip_macOS_GH_actions_downloads
41
+ def test_sxs_load_v2_levs():
42
+ # Check that a bad Lev raises an error
43
+ with pytest.raises(ValueError):
44
+ sxs.load("SXS:BBH:1001v2.0/Lev189", ignore_deprecation=True)
45
+ # Check that the default Lev works, and different Levs produce different metadata
46
+ assert (
47
+ sxs.load("SXS:BBH:1001v2.0").metadata.reference_time
48
+ == sxs.load("SXS:BBH:1001v2.0/Lev3").metadata.reference_time
49
+ )
50
+ assert (
51
+ sxs.load("SXS:BBH:1001v2.0/Lev1").metadata.reference_time
52
+ != sxs.load("SXS:BBH:1001v2.0/Lev3").metadata.reference_time
53
+ )
54
+ for lev, lev_number in [("", 3), ("/Lev3", 3), ("/Lev2", 2), ("/Lev1", 1)]:
55
+ s = sxs.load(f"SXS:BBH:1001v2.0{lev}", ignore_deprecation=True)
56
+ assert s.sxs_id_stem == "SXS:BBH:1001"
57
+ assert s.version == "v2.0"
58
+ assert s.lev_number == lev_number
59
+ s.h
60
+ s.strain
61
+ s.H
62
+ s.Strain
63
+ s.horizons
64
+ s.Horizons
65
+
66
+
67
+ @skip_macOS_GH_actions_downloads
68
+ def test_sxs_load_v3():
69
+ # # This is necessary only after the deprecation of SXS:BBH:4000v3.0
70
+ # with pytest.warns(UserWarning):
71
+ # s = sxs.load("SXS:BBH:4000v3.0")
72
+ s = sxs.load("SXS:BBH:4000v3.0")
73
+ assert s.sxs_id_stem == "SXS:BBH:4000"
74
+ assert s.version == "v3.0"
75
+ s.h
76
+ s.strain
77
+ s.H
78
+ s.Strain
79
+ s.horizons
80
+ s.Horizons
81
+ s.psi4
82
+ s.Psi4
83
+
84
+
85
+ @skip_macOS_GH_actions_downloads
86
+ def test_sxs_load_v3_levs():
87
+ # Check that a bad Lev raises an error
88
+ with pytest.raises(ValueError):
89
+ sxs.load("SXS:BBH:4000v3.0/Lev189", ignore_deprecation=True)
90
+ # Check that the default Lev works, and different Levs produce different metadata
91
+ assert ( # Check that the default Lev loads the highest Lev
92
+ sxs.load("SXS:BBH:4000v3.0", ignore_deprecation=True).metadata.reference_time
93
+ == sxs.load("SXS:BBH:4000v3.0/Lev3", ignore_deprecation=True).metadata.reference_time
94
+ )
95
+ assert ( # Check that different Levs have different reference times
96
+ sxs.load("SXS:BBH:4000v3.0/Lev1", ignore_deprecation=True).metadata.reference_time
97
+ != sxs.load("SXS:BBH:4000v3.0/Lev3", ignore_deprecation=True).metadata.reference_time
98
+ )
99
+ for lev, lev_number in [("", 3), ("/Lev3", 3), ("/Lev2", 2), ("/Lev1", 1)]:
100
+ s = sxs.load(f"SXS:BBH:4000v3.0{lev}", ignore_deprecation=True)
101
+ assert s.sxs_id_stem == "SXS:BBH:4000"
102
+ assert s.version == "v3.0"
103
+ assert s.lev_number == lev_number
104
+ s.h
105
+ s.strain
106
+ s.H
107
+ s.Strain
108
+ s.horizons
109
+ s.Horizons
110
+
111
+
112
+ @pytest.mark.many_downloads # runs only with pytest flag ` --run_many_downloads`
113
+ def test_sxs_load_v3_catalog():
114
+ # This test goes right up to the point of loading every simulation
115
+ # at each Lev with each extrapolation order, but doesn't download
116
+ # any data
117
+ from pathlib import Path
118
+
119
+ def fake_load_metadata(self):
120
+ metadata_path = self.metadata_path
121
+ if metadata_path not in self.files:
122
+ return False
123
+ if "link" not in self.files[metadata_path]:
124
+ return False
125
+ if self.files[metadata_path].get("size", 0) < 500:
126
+ return False
127
+ return True
128
+
129
+ def fake_load_horizons(self):
130
+ horizons_path = self.horizons_path
131
+ if horizons_path in self.files:
132
+ if "link" not in self.files[horizons_path]:
133
+ return False
134
+ else:
135
+ # Some simulations used the SXS ID as a prefix in file paths
136
+ # within the Zenodo upload in version 1.x of the catalog.
137
+ if (extended_location := f"{self.sxs_id_stem}/{horizons_path}") in self.files:
138
+ if "link" not in self.files[extended_location]:
139
+ return False
140
+ elif self.files[extended_location].get("size", 0) < 500:
141
+ return False
142
+ else:
143
+ return False
144
+ return True
145
+
146
+ def fake_load_waveform(self, file_name, group):
147
+ file_name = Path(file_name)
148
+ h5_path = str(file_name.with_suffix(".h5"))
149
+ json_path = str(file_name.with_suffix(".json"))
150
+ return bool(
151
+ self.files.get(h5_path, {}).get("link", False)
152
+ and self.files.get(h5_path, {}).get("size", 0) > 500
153
+ and self.files.get(json_path, {}).get("link", False)
154
+ and self.files.get(json_path, {}).get("size", 0) > 500
155
+ )
156
+
157
+ simulations = sxs.load("simulations")
158
+
159
+ success = True
160
+ for sxs_id, metadata in simulations.items():
161
+ if "NSNS" in sxs_id or "BHNS" in sxs_id:
162
+ continue
163
+
164
+ sim = sxs.load(sxs_id, ignore_deprecation=True)
165
+ if sim.version != "v3.0":
166
+ # print(
167
+ # f"Skipping {sxs_id} because its version is {sim.version} != v3.0"
168
+ # )
169
+ continue
170
+
171
+ for lev_number in metadata["lev_numbers"]:
172
+ for extrapolation in ["Outer", "N2", "N3", "N4"]:
173
+ sim = sxs.load(
174
+ f"{sxs_id}/Lev{lev_number}",
175
+ extrapolation=extrapolation,
176
+ ignore_deprecation=True,
177
+ )
178
+
179
+ if not fake_load_metadata(sim):
180
+ print(f"Failed to load {sxs_id}/Lev{lev_number} {extrapolation} metadata")
181
+ success = False
182
+
183
+ if not fake_load_horizons(sim):
184
+ print(f"Failed to load {sxs_id}/Lev{lev_number} {extrapolation} horizons")
185
+ success = False
186
+
187
+ if not fake_load_waveform(sim, *sim.strain_path):
188
+ print(f"Failed to load {sxs_id}/Lev{lev_number} {extrapolation} strain")
189
+ success = False
190
+
191
+ if not fake_load_waveform(sim, *sim.psi4_path):
192
+ print(f"Failed to load {sxs_id}/Lev{lev_number} {extrapolation} psi4")
193
+ success = False
194
+
195
+ assert success
196
+
197
+
198
+ @skip_macOS_GH_actions_downloads
199
+ @pytest.mark.parametrize("loader", [sxs.Simulation, sxs.load])
200
+ def test_superseding(loader):
201
+ simulation = "SXS:BBH:0001"
202
+
203
+ # No version, no arguments
204
+ with pytest.raises(ValueError):
205
+ loader(f"{simulation}")
206
+
207
+ # Version, no arguments
208
+ with pytest.warns(UserWarning):
209
+ s = loader(f"{simulation}v2.0")
210
+ assert s.sxs_id_stem == simulation
211
+
212
+ # No version, ignore_deprecation
213
+ s = loader(f"{simulation}", ignore_deprecation=True)
214
+ assert s.sxs_id_stem == simulation
215
+
216
+ # Version, ignore_deprecation
217
+ s = loader(f"{simulation}v2.0", ignore_deprecation=True)
218
+ assert s.sxs_id_stem == simulation
219
+
220
+ # No version, auto_supersede
221
+ with pytest.warns(UserWarning):
222
+ s = loader(f"{simulation}", auto_supersede=True)
223
+ assert s.sxs_id_stem != simulation
224
+
225
+ # Version, auto_supersede
226
+ with pytest.warns(UserWarning):
227
+ s = loader(f"{simulation}v2.0", auto_supersede=True)
228
+ assert s.sxs_id_stem == simulation
229
+
230
+ # No version, ignore_deprecation, auto_supersede
231
+ s = loader(f"{simulation}", ignore_deprecation=True, auto_supersede=True)
232
+ assert s.sxs_id_stem == simulation
233
+
234
+ # Version, ignore_deprecation, auto_supersede
235
+ s = loader(f"{simulation}v2.0", ignore_deprecation=True, auto_supersede=True)
236
+ assert s.sxs_id_stem == simulation
@@ -118,7 +118,7 @@ def test_dpa_rotated_generally(w, Rs):
118
118
  # smaller of the two sign options.
119
119
  assert (
120
120
  max(np.amin(np.vstack((np.linalg.norm(LL1 - LL2, axis=1), np.linalg.norm(LL1 + LL2, axis=1))), axis=0))
121
- < 1.0e-12
121
+ < 4.0e-12
122
122
  )
123
123
 
124
124
 
@@ -1 +0,0 @@
1
- __version__ = "2025.0.5"