sxs 2023.3.2__py3-none-any.whl → 2024.0.1__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 CHANGED
@@ -11,6 +11,9 @@ except ModuleNotFoundError: # pragma: no cover
11
11
 
12
12
  __version__ = importlib_metadata.version(__name__)
13
13
 
14
+ doi_prefix = "10.26138"
15
+ doi_url = f"https://doi.org/{doi_prefix}/"
16
+
14
17
  from . import utilities
15
18
  from .utilities import (
16
19
  file_format, sxs_directory, read_config, write_config,
@@ -26,7 +29,8 @@ from .waveforms import rotating_paired_xor_multishuffle_bzip2 as rpxmb
26
29
  from .waveforms import rotating_paired_diff_multishuffle_bzip2 as rpdmb
27
30
  from .waveforms import spectre_cce_v1
28
31
  from . import catalog, metadata, horizons, waveforms, zenodo, caltechdata
29
- from .handlers import load, loadcontext, load_lvc
32
+ from .simulations import Simulation, Simulations
33
+ from .handlers import load, load_via_sxs_id, loadcontext, load_lvc
30
34
 
31
35
  # The speed of light is, of course, defined to be exactly
32
36
  speed_of_light = 299_792_458.0 # m/s
@@ -50,7 +54,3 @@ solar_mass_parameter = 1.32712440041e20 # m^3/s^2
50
54
  # precision than is warranted by the measurement.
51
55
  m_sun_in_meters = 1476.6250385063112526099633973363 # m
52
56
  m_sun_in_seconds = 4.925490949162941425997992909269e-06 # s
53
-
54
-
55
- doi_prefix = "10.26138"
56
- doi_url = f"https://doi.org/{doi_prefix}/"
sxs/__version__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "2023.3.2"
1
+ __version__ = "2024.0.1"
sxs/catalog/catalog.py CHANGED
@@ -49,11 +49,20 @@ class Catalog(object):
49
49
 
50
50
  """
51
51
  import json
52
- import tempfile
53
52
  import zipfile
54
53
  from .. import sxs_directory, read_config
55
54
  from ..utilities import download_file
56
55
 
56
+ from warnings import warn
57
+ deprecation_notice = """
58
+
59
+ You have called a function that uses the `Catalog` class,
60
+ which, as of `sxs` version 2024.0.0, has been deprecated in
61
+ favor of the `Simulations` interface. See the documentation
62
+ for more information.
63
+ """
64
+ warn(deprecation_notice)
65
+
57
66
  progress = read_config("download_progress", True)
58
67
 
59
68
  cache_path = sxs_directory("cache") / "catalog.zip"
sxs/handlers.py CHANGED
@@ -1,7 +1,25 @@
1
1
  """Functions to facilitate generic handling of SXS-format data files"""
2
2
 
3
3
  import contextlib
4
- from . import waveforms
4
+ from . import waveforms, doi_url
5
+
6
+
7
+ class JSONHandler:
8
+ """Utility for loading and saving JSON files"""
9
+ @classmethod
10
+ def load(cls, file, **kwargs):
11
+ import json
12
+ if hasattr(file, "read"):
13
+ return json.load(file, **kwargs)
14
+ with open(file, "r") as f:
15
+ return json.load(f, **kwargs)
16
+ @classmethod
17
+ def save(cls, obj, file, **kwargs):
18
+ import json
19
+ if hasattr(file, "write"):
20
+ return json.dump(obj, file, **kwargs)
21
+ with open(file, "w") as f:
22
+ return json.dump(obj, f, **kwargs)
5
23
 
6
24
 
7
25
  def sxs_handler(format_string):
@@ -42,6 +60,8 @@ def sxs_handler(format_string):
42
60
  elif format_string.lower().startswith("waveforms"):
43
61
  format_string = re.sub(r"^waveforms\.?", "", format_string, count=1, flags=re.IGNORECASE)
44
62
  return waveforms.formats.get(format_string, waveforms.formats[None])
63
+ elif format_string.lower() == "json":
64
+ return JSONHandler
45
65
  else:
46
66
  format_list = [
47
67
  catalog.formats,
@@ -97,12 +117,14 @@ def sxs_loader(file, group=None):
97
117
  file_string = str(pathlib.Path(file).name).lower()
98
118
  if "catalog" in file_string:
99
119
  format_string = "catalog"
100
- elif "metadata" in file_string:
120
+ elif file_string.startswith("metadata"):
101
121
  format_string = "metadata"
102
122
  elif "horizons" in file_string:
103
123
  format_string = "horizons"
104
124
  elif re.match("(rh_|rhoverm_|rpsi4_|rmpsi4_)", file_string):
105
125
  format_string = "waveforms"
126
+ elif file_string.endswith("json"):
127
+ format_string = "json"
106
128
  else:
107
129
  raise ValueError(f"File '{file}' contains no recognized format information")
108
130
  handler = sxs_handler(format_string)
@@ -110,74 +132,111 @@ def sxs_loader(file, group=None):
110
132
  return handler.load
111
133
 
112
134
 
113
- def load(location, download=None, cache=None, progress=None, **kwargs):
135
+ def _safe_resolve_exists(path):
136
+ """Evaluate `path.resolve().exists()` without throwing exception
137
+
138
+ This is just here to work around a bug that turned up in Windows
139
+ on python 3.8. It's not clear if it turns up in other versions.
140
+ """
141
+ try:
142
+ return path.resolve().exists()
143
+ except:
144
+ return False
145
+
146
+ def load(location, download=None, cache=None, progress=None, truepath=None, **kwargs):
114
147
  """Load an SXS-format dataset, optionally downloading and caching
115
148
 
116
- The dataset can be the full catalog of all SXS simulations, or metadata,
117
- horizon data, or a waveform from an individual simulation.
149
+ The dataset can be the full catalog of all SXS simulations, or
150
+ metadata, horizon data, or a waveform from an individual
151
+ simulation.
118
152
 
119
153
  Parameters
120
154
  ----------
121
155
  location : {str, pathlib.Path}
122
- A local file path, URL, SXS path, or SXS path pattern. See Notes below.
156
+ A local file path, URL, SXS path, or SXS path pattern. See
157
+ Notes below.
123
158
  download : {None, bool}, optional
124
- If this is True and the data is recognized as starting with an SXS ID but
125
- cannot be found in the cache, the data will be downloaded automatically.
126
- If this is None (the default) and an SXS configuration file is found with a
127
- `download` key, that value will be used. If this is False, any
128
- configuration will be ignored, and no files will be downloaded. Note that
129
- if this is True but `cache` is None, `cache` will automatically be switched
130
- to True.
159
+ If this is True and the data is recognized as starting with an
160
+ SXS ID but cannot be found in the cache, the data will be
161
+ downloaded automatically. If this is None (the default) and
162
+ an SXS configuration file is found with a `download` key, that
163
+ value will be used. If this is False, any configuration will
164
+ be ignored, and no files will be downloaded. Note that if
165
+ this is True but `cache` is None, `cache` will automatically
166
+ be switched to True.
131
167
  cache : {None, bool}, optional
132
- The cache directory is determined by `sxs.sxs_directory`, and any downloads
133
- will be stored in that directory. If this is None (the default) and
134
- `download` is True it will be set to True. If this is False, any
135
- configuration will be ignored and any files will be downloaded to a
136
- temporary directory that will be deleted when python exits.
168
+ The cache directory is determined by `sxs.sxs_directory`, and
169
+ any downloads will be stored in that directory. If this is
170
+ None (the default) and `download` is True it will be set to
171
+ True. If this is False, any configuration will be ignored and
172
+ any files will be downloaded to a temporary directory that
173
+ will be deleted when python exits.
137
174
  progress : {None, bool}, optional
138
- If True, full file names will be shown and, if a nonzero Content-Length
139
- header is returned, a progress bar will be shown during any downloads.
140
- Default is None, which just reads the configuration value with
141
- `read_config("download_progress", True)`, defaulting to True.
175
+ If True, full file names will be shown and, if a nonzero
176
+ Content-Length header is returned, a progress bar will be
177
+ shown during any downloads. Default is None, which just reads
178
+ the configuration value with `read_config("download_progress",
179
+ True)`, defaulting to True.
180
+ truepath : {None, str}, optional
181
+ If the file is downloaded, this allows the output path to be
182
+ overridden, rather than selected automatically. The output
183
+ path will be stored in `truepath` relative to the cache
184
+ directory.
142
185
 
143
186
  Keyword Parameters
144
187
  ------------------
145
- All remaining parameters are passed to the `load` function responsible for the
146
- requested data.
188
+ All remaining parameters are passed to the `load` function
189
+ responsible for the requested data.
147
190
 
148
191
  See Also
149
192
  --------
150
193
  sxs.sxs_directory : Locate configuration and cache files
151
- sxs.write_config : Set defaults for `download` and `cache` parameters
194
+ sxs.write_config : Set defaults for `download` and `cache`
195
+ parameters
152
196
 
153
197
  Notes
154
198
  -----
155
199
  This function can load data in various ways.
156
200
 
157
- 1) Given an absolute or relative path to a local file, it just loads the data
158
- directly.
201
+ 1) Given an absolute or relative path to a local file, it just
202
+ loads the data directly.
203
+
204
+ 2) If `truepath` is set, and points to a file that exists —
205
+ whether absolute, relative to the current working directory,
206
+ or relative to the cache directory — that file will be
207
+ loaded.
159
208
 
160
- 2) If `location` is a valid URL including the scheme (https://, or http://),
161
- it will be downloaded regardless of the `download` parameter and
162
- optionally cached.
209
+ 3) If `location` is a valid URL including the scheme (https://,
210
+ or http://), it will be downloaded regardless of the
211
+ `download` parameter and optionally cached.
163
212
 
164
- 3) Given an SXS path — like 'SXS:BBH:1234/Lev5/h_Extrapolated_N2.h5' — the
165
- file is located in the catalog for details. This function then looks in
166
- the local cache directory and loads it if present.
213
+ 4) Given an SXS simulation specification — like "SXS:BBH:1234",
214
+ "SXS:BBH:1234v2.0", "SXS:BBH:1234/Lev5", or
215
+ "SXS:BBH:1234v2.0/Lev5" the simulation is loaded as an
216
+ `sxs.Simulation` object.
167
217
 
168
- 4) If the SXS path is not found in the cache directory and `download` is set
169
- to `True` (when this function is called, or in the sxs config file) this
170
- function attempts to download the data. Note that `download` must be
171
- explicitly set in this case, or a ValueError will be raised.
218
+ 5) Given an SXS path like
219
+ "SXS:BBH:1234/Lev5/h_Extrapolated_N2.h5" the file is
220
+ located in the catalog for details. This function then looks
221
+ in the local cache directory and loads it if present.
172
222
 
173
- Note that downloading is switched off by default, but if it is switched on (set
174
- to True), the cache is also switched on by default.
223
+ 6) If the SXS path is not found in the cache directory and
224
+ `download` is set to `True` (when this function is called, or
225
+ in the sxs config file) this function attempts to download
226
+ the data. Note that `download` must be explicitly set in
227
+ this case, or a ValueError will be raised.
228
+
229
+ If the file is downloaded, it will be stored in the cache
230
+ according to the `location`, unless `truepath` is set as noted
231
+ above, in which case it is stored there. Note that downloading is
232
+ switched off by default, but if it is switched on (set to True),
233
+ the cache is also switched on by default.
175
234
 
176
235
  """
177
236
  import pathlib
178
237
  import urllib.request
179
- from . import Catalog, read_config, sxs_directory
180
- from .utilities import url, download_file, sxs_path_to_system_path
238
+ from . import Simulations, Simulation, read_config, sxs_directory, Catalog
239
+ from .utilities import url, download_file, sxs_path_to_system_path, sxs_id_version_lev_exact_re
181
240
 
182
241
  # Note: `download` and/or `cache` may still be `None` after this
183
242
  if download is None:
@@ -197,26 +256,38 @@ def load(location, download=None, cache=None, progress=None, **kwargs):
197
256
  json_path = path.with_suffix('.json')
198
257
 
199
258
  if not path.exists():
200
- if h5_path.resolve().exists():
259
+ if truepath and (testpath := pathlib.Path(truepath).expanduser()).exists():
260
+ path = testpath
261
+
262
+ elif truepath and (testpath := cache_path / truepath).exists():
263
+ path = testpath
264
+
265
+ elif _safe_resolve_exists(h5_path):
201
266
  path = h5_path
202
267
 
203
- elif json_path.resolve().exists():
268
+ elif _safe_resolve_exists(json_path):
204
269
  path = json_path
205
270
 
206
271
  elif "scheme" in url.parse(location):
207
272
  m = url.parse(location)
208
- path_name = urllib.request.url2pathname(f"{m['host']}/{m['port']}/{m['resource']}")
209
- path = cache_path / path_name
273
+ truepath = truepath or urllib.request.url2pathname(f"{m['host']}/{m['port']}/{m['resource']}")
274
+ path = cache_path / truepath
210
275
  if not path.resolve().exists():
211
276
  if download is False: # Again, we want literal False, not casting to False
212
- raise ValueError(f"File '{path_name}' not found in cache, but downloading turned off")
277
+ raise ValueError(f"File '{truepath}' not found in cache, but downloading turned off")
213
278
  download_file(location, path, progress=progress)
214
279
 
215
280
  elif location == "catalog":
216
281
  return Catalog.load(download=download)
217
282
 
283
+ elif location == "simulations":
284
+ return Simulations.load(download=download)
285
+
286
+ elif sxs_id_version_lev_exact_re.match(location):
287
+ return Simulation(location, download=download, cache=cache, progress=progress, **kwargs)
288
+
218
289
  else:
219
- # Try to find an appropriate SXS file
290
+ # Try to find an appropriate SXS file in the catalog
220
291
  catalog = Catalog.load(download=download)
221
292
  selections = catalog.select_files(location)
222
293
  if not selections:
@@ -226,7 +297,7 @@ def load(location, download=None, cache=None, progress=None, **kwargs):
226
297
  print(" " + "\n ".join(selections))
227
298
  paths = []
228
299
  for sxs_path, file_info in selections.items():
229
- truepath = sxs_path_to_system_path(file_info.get("truepath", sxs_path))
300
+ truepath = truepath or sxs_path_to_system_path(file_info.get("truepath", sxs_path))
230
301
  path = cache_path / truepath
231
302
  if not path.resolve().exists():
232
303
  download_url = file_info["download"]
@@ -243,6 +314,37 @@ def load(location, download=None, cache=None, progress=None, **kwargs):
243
314
  return loader(path, **kwargs)
244
315
 
245
316
 
317
+ def load_via_sxs_id(sxsid, location, *, download=None, cache=None, progress=None, truepath=None, **kwargs):
318
+ """Load a path via a (possibly versioned) SXS ID
319
+
320
+ Given some SXS ID like "SXS:BBH:1234" or a versioned ID like
321
+ "SXS:BBH:1234v2.0", we may wish to first resolve the DOI to a
322
+ specific Zenodo record, and then load a specific path under that
323
+ record — for example, we may want to export the Zenodo record as
324
+ JSON by appending "export/json" to the Zenodo URL, which we
325
+ *could* get as
326
+
327
+ load("https://zenodo.org/records/13152488/export/json")
328
+
329
+ because that's the record "SXS:BBH:1234v2.0" resolves to.
330
+ However, we don't want to keep track of the Zenodo URL, so we
331
+ just use this function instead, as
332
+
333
+ load_via_sxs_id("SXS:BBH:1234v2.0", "export/json")
334
+
335
+ """
336
+ from pathlib import Path
337
+ import requests
338
+ from .utilities import sxs_path_to_system_path
339
+ url = f"{doi_url}{sxsid}"
340
+ response = requests.head(url, allow_redirects=True)
341
+ if response.status_code != 200:
342
+ raise ValueError(f"Could not load via DOI {url=}")
343
+ final_url = f"{response.url}/{location}"
344
+ truepath = truepath or Path(sxs_path_to_system_path(sxsid)) / location
345
+ return load(final_url, download, cache, progress, truepath, **kwargs)
346
+
347
+
246
348
  @contextlib.contextmanager
247
349
  def loadcontext(*args, **kwargs):
248
350
  """Context manager for backwards compatibility
sxs/horizons/__init__.py CHANGED
@@ -308,7 +308,7 @@ class Horizons(object):
308
308
  )
309
309
 
310
310
  """
311
- from scipy.integrate import simps
311
+ from scipy.integrate import simpson
312
312
 
313
313
  t = self.A.time
314
314
  com = self.newtonian_com
@@ -319,8 +319,8 @@ class Horizons(object):
319
319
  i_i, i_f = np.argmin(np.abs(t - t_i)), np.argmin(np.abs(t - t_f))
320
320
 
321
321
  # Find the optimum analytically
322
- com_0 = simps(com[i_i : i_f + 1], t[i_i : i_f + 1], axis=0)
323
- com_1 = simps((t[:, np.newaxis] * com)[i_i : i_f + 1], t[i_i : i_f + 1], axis=0)
322
+ com_0 = simpson(com[i_i : i_f + 1], x=t[i_i : i_f + 1], axis=0)
323
+ com_1 = simpson((t[:, np.newaxis] * com)[i_i : i_f + 1], x=t[i_i : i_f + 1], axis=0)
324
324
  x_i = 2 * (com_0 * (2 * t_f ** 3 - 2 * t_i ** 3) + com_1 * (-3 * t_f ** 2 + 3 * t_i ** 2)) / (t_f - t_i) ** 4
325
325
  v_i = 6 * (com_0 * (-t_f - t_i) + 2 * com_1) / (t_f - t_i) ** 3
326
326
 
sxs/juliapkg.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
- "julia": "1.9",
2
+ "julia": "1.10",
3
3
  "packages": {
4
4
  "PostNewtonian": {
5
5
  "uuid": "377afc40-5642-4616-8613-b7ebca523866",
6
- "version": "0.9.1"
6
+ "version": "0.10.0"
7
7
  }
8
8
  }
9
9
  }
@@ -0,0 +1,2 @@
1
+ from .simulation import Simulation
2
+ from .simulations import Simulations