sxs 2024.0.27__tar.gz → 2024.0.28__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.
- {sxs-2024.0.27 → sxs-2024.0.28}/.github/workflows/build.yml +2 -2
- {sxs-2024.0.27 → sxs-2024.0.28}/CITATION.cff +2 -2
- {sxs-2024.0.27 → sxs-2024.0.28}/PKG-INFO +4 -4
- {sxs-2024.0.27 → sxs-2024.0.28}/pyproject.toml +3 -3
- sxs-2024.0.28/sxs/__version__.py +1 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/waveforms/alignment.py +263 -30
- sxs-2024.0.27/sxs/__version__.py +0 -1
- {sxs-2024.0.27 → sxs-2024.0.28}/.codecov.yml +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/.github/dependabot.yml +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/.github/scripts/parse_bump_rule.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/.github/workflows/pr_rtd_link.yml +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/.gitignore +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/.readthedocs.yaml +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/LICENSE +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/README.md +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/docs/api/catalog.md +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/docs/api/horizons.md +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/docs/api/load.md +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/docs/api/metadata.md +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/docs/api/simulation.md +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/docs/api/simulations.md +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/docs/api/time_series.md +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/docs/api/waveforms.md +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/docs/html/main.html +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/docs/images/favicon.ico +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/docs/index.md +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/docs/javascript/mathjax.js +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/docs/julia.md +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/docs/mathematica.md +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/docs/stylesheets/extra.css +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/docs/tutorials/00-Introduction.ipynb +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/docs/tutorials/01-Simulations_and_Metadata.ipynb +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/docs/tutorials/02-Simulation.ipynb +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/docs/tutorials/03-Horizons.ipynb +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/docs/tutorials/04-Waveforms.ipynb +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/docs/tutorials/05-PreprocessingForFFTs.ipynb +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/mkdocs.yml +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/__init__.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/caltechdata/__init__.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/caltechdata/catalog.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/caltechdata/login.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/catalog/__init__.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/catalog/catalog.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/catalog/create.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/catalog/description.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/handlers.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/horizons/__init__.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/horizons/spec_horizons_h5.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/horizons/xor_multishuffle_bzip2.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/julia/GWFrames.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/julia/__init__.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/juliapkg.json +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/metadata/__init__.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/metadata/metadata.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/simulations/__init__.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/simulations/local.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/simulations/simulation.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/simulations/simulations.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/time_series.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/utilities/__init__.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/utilities/bitwise.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/utilities/decimation/__init__.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/utilities/decimation/greedy_spline.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/utilities/decimation/linear_bisection.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/utilities/decimation/peak_greed.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/utilities/decimation/suppression.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/utilities/dicts.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/utilities/downloads.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/utilities/files.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/utilities/formats.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/utilities/inspire.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/utilities/lvcnr/__init__.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/utilities/lvcnr/comparisons.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/utilities/lvcnr/conversion.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/utilities/lvcnr/dataset.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/utilities/lvcnr/horizons.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/utilities/lvcnr/metadata.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/utilities/lvcnr/waveform_amp_phase.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/utilities/lvcnr/waveforms.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/utilities/monotonicity.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/utilities/pretty_print.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/utilities/references/__init__.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/utilities/references/ads.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/utilities/references/arxiv.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/utilities/references/fairchild_report.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/utilities/references/inspire.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/utilities/references/journal_abbreviations.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/utilities/references/references.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/utilities/select.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/utilities/smooth_functions.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/utilities/sxs_directories.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/utilities/sxs_identifiers.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/utilities/url.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/waveforms/__init__.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/waveforms/format_handlers/__init__.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/waveforms/format_handlers/grathena.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/waveforms/format_handlers/lvc.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/waveforms/format_handlers/nrar.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/waveforms/format_handlers/rotating_paired_diff_multishuffle_bzip2.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/waveforms/format_handlers/rotating_paired_xor_multishuffle_bzip2.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/waveforms/format_handlers/spectre_cce_v1.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/waveforms/memory.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/waveforms/mode_utilities.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/waveforms/transformations.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/waveforms/waveform_grid.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/waveforms/waveform_mixin.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/waveforms/waveform_modes.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/waveforms/waveform_signal.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/zenodo/__init__.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/zenodo/api/__init__.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/zenodo/api/deposit.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/zenodo/api/login.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/zenodo/api/records.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/zenodo/catalog.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/zenodo/creators.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/zenodo/simannex.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/sxs/zenodo/surrogatemodeling.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/tests/__init__.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/tests/conftest.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/tests/test_catalog.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/tests/test_horizons.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/tests/test_julia.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/tests/test_loader.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/tests/test_metadata.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/tests/test_simulation.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/tests/test_time_series.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/tests/test_transformations.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/tests/test_utilities.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/tests/test_waveform_rotations.py +0 -0
- {sxs-2024.0.27 → sxs-2024.0.28}/tests/test_waveforms.py +0 -0
|
@@ -30,12 +30,12 @@ jobs:
|
|
|
30
30
|
fail-fast: false
|
|
31
31
|
matrix:
|
|
32
32
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
33
|
-
python-version: ['3.
|
|
33
|
+
python-version: ['3.10', '3.12']
|
|
34
34
|
|
|
35
35
|
steps:
|
|
36
36
|
- name: Skip replicates on main branch
|
|
37
37
|
env:
|
|
38
|
-
skip_replicates: ${{ github.ref == 'refs/heads/main' &&
|
|
38
|
+
skip_replicates: ${{ github.ref == 'refs/heads/main' && matrix.os != 'ubuntu-latest' }}
|
|
39
39
|
shell: bash
|
|
40
40
|
run: |
|
|
41
41
|
echo "skipping_build_and_test_replicate=${skip_replicates}" >> $GITHUB_ENV
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sxs
|
|
3
|
-
Version: 2024.0.
|
|
3
|
+
Version: 2024.0.28
|
|
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/
|
|
@@ -34,17 +34,17 @@ Classifier: Operating System :: OS Independent
|
|
|
34
34
|
Classifier: Programming Language :: Python :: 3
|
|
35
35
|
Classifier: Topic :: Scientific/Engineering :: Astronomy
|
|
36
36
|
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
37
|
-
Requires-Python: >=3.
|
|
37
|
+
Requires-Python: >=3.10
|
|
38
38
|
Requires-Dist: h5py>=3
|
|
39
39
|
Requires-Dist: inflection>=0.5.1
|
|
40
40
|
Requires-Dist: juliacall>=0.9.20
|
|
41
41
|
Requires-Dist: numba>=0.55; implementation_name == 'cpython'
|
|
42
|
-
Requires-Dist: numpy>=1.
|
|
42
|
+
Requires-Dist: numpy>=1.25
|
|
43
43
|
Requires-Dist: pandas>=1.1.2
|
|
44
44
|
Requires-Dist: pytz>=2020.1
|
|
45
45
|
Requires-Dist: quaternionic>=1.0
|
|
46
46
|
Requires-Dist: requests>=2.24.0
|
|
47
|
-
Requires-Dist: scipy>=1.
|
|
47
|
+
Requires-Dist: scipy>=1.13
|
|
48
48
|
Requires-Dist: spherical>=1.0.15
|
|
49
49
|
Requires-Dist: tqdm>=4.63.1
|
|
50
50
|
Requires-Dist: urllib3>=1.25.10
|
|
@@ -3,7 +3,7 @@ name = "sxs"
|
|
|
3
3
|
dynamic = ["version"]
|
|
4
4
|
description = "Interface to data produced by the Simulating eXtreme Spacetimes collaboration"
|
|
5
5
|
readme = "README.md"
|
|
6
|
-
requires-python = ">=3.
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
7
|
license = {file = "LICENSE"}
|
|
8
8
|
authors = [
|
|
9
9
|
{ name = "Michael Boyle", email = "michael.oliver.boyle@gmail.com" }
|
|
@@ -18,8 +18,8 @@ classifiers = [
|
|
|
18
18
|
"Topic :: Scientific/Engineering :: Astronomy"
|
|
19
19
|
]
|
|
20
20
|
dependencies = [
|
|
21
|
-
"numpy >=1.
|
|
22
|
-
"scipy >=1.
|
|
21
|
+
"numpy >=1.25",
|
|
22
|
+
"scipy >=1.13",
|
|
23
23
|
"numba >=0.55; implementation_name == 'cpython'",
|
|
24
24
|
"quaternionic >=1.0",
|
|
25
25
|
"spherical >=1.0.15",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2024.0.28"
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import numpy as np
|
|
2
2
|
|
|
3
|
+
import quaternionic
|
|
4
|
+
|
|
3
5
|
from scipy.integrate import trapezoid
|
|
4
6
|
|
|
5
7
|
import multiprocessing as mp
|
|
6
8
|
from functools import partial
|
|
7
9
|
|
|
8
10
|
|
|
9
|
-
|
|
10
11
|
def align1d(wa, wb, t1, t2, n_brute_force=None):
|
|
11
12
|
"""Align waveforms by shifting in time
|
|
12
13
|
|
|
@@ -77,7 +78,6 @@ def align1d(wa, wb, t1, t2, n_brute_force=None):
|
|
|
77
78
|
|
|
78
79
|
"""
|
|
79
80
|
from scipy.interpolate import CubicSpline
|
|
80
|
-
from scipy.integrate import trapezoid
|
|
81
81
|
from scipy.optimize import least_squares
|
|
82
82
|
|
|
83
83
|
# Check that (t1, t2) makes sense and is actually contained in both waveforms
|
|
@@ -89,8 +89,8 @@ def align1d(wa, wb, t1, t2, n_brute_force=None):
|
|
|
89
89
|
raise ValueError(f"(t1,t2)=({t1}, {t2}) not contained in wb.t, which spans ({wb.t[0]}, {wb.t[-1]})")
|
|
90
90
|
|
|
91
91
|
# Figure out time offsets to try
|
|
92
|
-
δt_lower = max(t1 - t2, wb.t[
|
|
93
|
-
δt_upper = min(t2 - t1, wb.t[
|
|
92
|
+
δt_lower = max(t1 - t2, t2 - wb.t[-1])
|
|
93
|
+
δt_upper = min(t2 - t1, t1 - wb.t[0])
|
|
94
94
|
|
|
95
95
|
# We'll start by brute forcing, sampling time offsets evenly at as many
|
|
96
96
|
# points as there are time steps in (t1,t2) in the input waveforms
|
|
@@ -126,15 +126,13 @@ def _cost2d(δt_δϕ, args):
|
|
|
126
126
|
|
|
127
127
|
# Take the sqrt because least_squares squares the inputs...
|
|
128
128
|
diff = trapezoid(
|
|
129
|
-
np.sum(
|
|
130
|
-
abs(modes_A(t_reference + δt) * np.exp(1j * m * δϕ) * δΨ_factor - modes_B) ** 2, axis=1
|
|
131
|
-
),
|
|
129
|
+
np.sum(abs(modes_A(t_reference + δt) * np.exp(1j * m * δϕ) * δΨ_factor - modes_B) ** 2, axis=1),
|
|
132
130
|
t_reference,
|
|
133
131
|
)
|
|
134
132
|
return np.sqrt(diff / normalization)
|
|
135
133
|
|
|
136
134
|
|
|
137
|
-
def align2d(wa, wb, t1, t2, n_brute_force_δt=None, n_brute_force_δϕ=5, include_modes=None, nprocs=None):
|
|
135
|
+
def align2d(wa, wb, t1, t2, n_brute_force_δt=None, n_brute_force_δϕ=5, max_δt=np.inf, use_δΨ=False, include_modes=None, nprocs=None):
|
|
138
136
|
"""Align waveforms by shifting in time and phase
|
|
139
137
|
|
|
140
138
|
This function determines the optimal time and phase offset to apply to `wa` by
|
|
@@ -166,7 +164,7 @@ def align2d(wa, wb, t1, t2, n_brute_force_δt=None, n_brute_force_δϕ=5, includ
|
|
|
166
164
|
Waveforms to be aligned
|
|
167
165
|
t1 : float
|
|
168
166
|
t2 : float
|
|
169
|
-
Beginning and end of integration interval
|
|
167
|
+
Beginning and end of integration interval.
|
|
170
168
|
n_brute_force_δt : int, optional
|
|
171
169
|
Number of evenly spaced δt values between (t1-t2) and (t2-t1) to sample
|
|
172
170
|
for the initial guess. By default, this is just the maximum number of
|
|
@@ -179,6 +177,10 @@ def align2d(wa, wb, t1, t2, n_brute_force_δt=None, n_brute_force_δϕ=5, includ
|
|
|
179
177
|
is not the default because, even though it is formally the right
|
|
180
178
|
thing to do, it takes much longer to produce the same result
|
|
181
179
|
as just using 5, which is much faster.
|
|
180
|
+
max_δt : float, optional
|
|
181
|
+
Max δt to allow for when choosing the initial guess.
|
|
182
|
+
use_δΨ : float, optional
|
|
183
|
+
Whether or not to allow for a h -> -h transformation.
|
|
182
184
|
include_modes: list, optional
|
|
183
185
|
A list containing the (ell, m) modes to be included in the L² norm.
|
|
184
186
|
nprocs: int, optional
|
|
@@ -218,23 +220,19 @@ def align2d(wa, wb, t1, t2, n_brute_force_δt=None, n_brute_force_δϕ=5, includ
|
|
|
218
220
|
if t2 <= t1:
|
|
219
221
|
raise ValueError(f"(t1,t2)=({t1}, {t2}) is out of order")
|
|
220
222
|
if wa.t[0] > t1 or wa.t[-1] < t2:
|
|
221
|
-
raise ValueError(
|
|
222
|
-
f"(t1,t2)=({t1}, {t2}) not contained in wa.t, which spans ({wa.t[0]}, {wa.t[-1]})"
|
|
223
|
-
)
|
|
223
|
+
raise ValueError(f"(t1,t2)=({t1}, {t2}) not contained in wa.t, which spans ({wa.t[0]}, {wa.t[-1]})")
|
|
224
224
|
if wb.t[0] > t1 or wb.t[-1] < t2:
|
|
225
|
-
raise ValueError(
|
|
226
|
-
f"(t1,t2)=({t1}, {t2}) not contained in wb.t, which spans ({wb.t[0]}, {wb.t[-1]})"
|
|
227
|
-
)
|
|
225
|
+
raise ValueError(f"(t1,t2)=({t1}, {t2}) not contained in wb.t, which spans ({wb.t[0]}, {wb.t[-1]})")
|
|
228
226
|
|
|
229
227
|
# Figure out time offsets to try
|
|
230
|
-
δt_lower = max(t1 - t2, wa.t[
|
|
231
|
-
δt_upper = min(t2 - t1, wa.t[
|
|
232
|
-
|
|
228
|
+
δt_lower = max(-max_δt, max(t1 - t2, t2 - wa.t[-1]))
|
|
229
|
+
δt_upper = min(max_δt, min(t2 - t1, t1 - wa.t[0]))
|
|
230
|
+
|
|
233
231
|
# We'll start by brute forcing, sampling time offsets evenly at as many
|
|
234
232
|
# points as there are time steps in (t1,t2) in the input waveforms
|
|
235
233
|
if n_brute_force_δt is None:
|
|
236
234
|
n_brute_force_δt = max(sum((wa.t >= t1) & (wa.t <= t2)), sum((wb.t >= t1) & (wb.t <= t2)))
|
|
237
|
-
δt_brute_force = np.linspace(δt_lower, δt_upper, num=n_brute_force_δt)
|
|
235
|
+
δt_brute_force = np.array(list(np.linspace(δt_lower, δt_upper, num=n_brute_force_δt)) + [0.0])
|
|
238
236
|
|
|
239
237
|
if n_brute_force_δϕ is None:
|
|
240
238
|
n_brute_force_δϕ = 2 * wa.ell_max + 1
|
|
@@ -246,7 +244,7 @@ def align2d(wa, wb, t1, t2, n_brute_force_δt=None, n_brute_force_δϕ=5, includ
|
|
|
246
244
|
|
|
247
245
|
# Remove certain modes, if requested
|
|
248
246
|
ell_max = min(wa.ell_max, wb.ell_max)
|
|
249
|
-
if include_modes
|
|
247
|
+
if include_modes is not None:
|
|
250
248
|
for L in range(2, ell_max + 1):
|
|
251
249
|
for M in range(-L, L + 1):
|
|
252
250
|
if not (L, M) in include_modes:
|
|
@@ -254,17 +252,11 @@ def align2d(wa, wb, t1, t2, n_brute_force_δt=None, n_brute_force_δϕ=5, includ
|
|
|
254
252
|
wb.data[:, wb.index(L, M)] *= 0
|
|
255
253
|
|
|
256
254
|
# Define the cost function
|
|
257
|
-
modes_A = CubicSpline(
|
|
258
|
-
|
|
259
|
-
)
|
|
260
|
-
modes_B = CubicSpline(
|
|
261
|
-
wb.t, wb[:, wb.index(2, -2) : wb.index(ell_max + 1, -(ell_max + 1))].data
|
|
262
|
-
)(t_reference)
|
|
255
|
+
modes_A = CubicSpline(wa.t, wa[:, wa.index(2, -2) : wa.index(ell_max + 1, -(ell_max + 1))].data)
|
|
256
|
+
modes_B = CubicSpline(wb.t, wb[:, wb.index(2, -2) : wb.index(ell_max + 1, -(ell_max + 1))].data)(t_reference)
|
|
263
257
|
|
|
264
258
|
normalization = trapezoid(
|
|
265
|
-
CubicSpline(
|
|
266
|
-
wb.t, wb[:, wb.index(2, -2) : wb.index(ell_max + 1, -(ell_max + 1))].norm ** 2
|
|
267
|
-
)(t_reference),
|
|
259
|
+
CubicSpline(wb.t, wb[:, wb.index(2, -2) : wb.index(ell_max + 1, -(ell_max + 1))].norm ** 2)(t_reference),
|
|
268
260
|
t_reference,
|
|
269
261
|
)
|
|
270
262
|
|
|
@@ -272,7 +264,10 @@ def align2d(wa, wb, t1, t2, n_brute_force_δt=None, n_brute_force_δϕ=5, includ
|
|
|
272
264
|
|
|
273
265
|
optimums = []
|
|
274
266
|
wa_primes = []
|
|
275
|
-
|
|
267
|
+
δΨ_factors = [1]
|
|
268
|
+
if use_δΨ:
|
|
269
|
+
δΨ_factors = [-1, +1]
|
|
270
|
+
for δΨ_factor in δΨ_factors:
|
|
276
271
|
# Optimize by brute force with multiprocessing
|
|
277
272
|
cost_wrapper = partial(_cost2d, args=[modes_A, modes_B, t_reference, m, δΨ_factor, normalization])
|
|
278
273
|
|
|
@@ -310,3 +305,241 @@ def align2d(wa, wb, t1, t2, n_brute_force_δt=None, n_brute_force_δϕ=5, includ
|
|
|
310
305
|
idx = np.argmin(abs(np.array([optimum.cost for optimum in optimums])))
|
|
311
306
|
|
|
312
307
|
return optimums[idx].cost, wa_primes[idx], optimums[idx]
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def _cost4d(δt_δso3, args):
|
|
311
|
+
from .. import WaveformModes
|
|
312
|
+
|
|
313
|
+
modes_A, modes_B, t_reference, normalization = args
|
|
314
|
+
δt = δt_δso3[0]
|
|
315
|
+
δSO3 = np.exp(quaternionic.array.from_vector_part(δt_δso3[1:]))
|
|
316
|
+
|
|
317
|
+
modes_A_at_δt = modes_A(t_reference + δt)
|
|
318
|
+
ell_max = int(np.sqrt(modes_A_at_δt.shape[1] + 4)) - 1
|
|
319
|
+
|
|
320
|
+
wa_prime = WaveformModes(
|
|
321
|
+
input_array=(modes_A_at_δt),
|
|
322
|
+
time=t_reference,
|
|
323
|
+
time_axis=0,
|
|
324
|
+
modes_axis=1,
|
|
325
|
+
ell_min=2,
|
|
326
|
+
ell_max=ell_max,
|
|
327
|
+
spin_weight=-2,
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
wa_prime = wa_prime.rotate(δSO3)
|
|
331
|
+
|
|
332
|
+
# Take the sqrt because least_squares squares the inputs...
|
|
333
|
+
diff = trapezoid(
|
|
334
|
+
np.sum(abs(wa_prime.data - modes_B) ** 2, axis=1),
|
|
335
|
+
t_reference,
|
|
336
|
+
)
|
|
337
|
+
return np.sqrt(diff / normalization)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def align4d(
|
|
341
|
+
wa,
|
|
342
|
+
wb,
|
|
343
|
+
t1,
|
|
344
|
+
t2,
|
|
345
|
+
n_brute_force_δt=1_000,
|
|
346
|
+
n_brute_force_δϕ=None,
|
|
347
|
+
max_δt=np.inf,
|
|
348
|
+
include_modes=None,
|
|
349
|
+
align2d_first=False,
|
|
350
|
+
nprocs=None,
|
|
351
|
+
):
|
|
352
|
+
"""Align waveforms by optimizing over a time translation and an SO(3) rotation.
|
|
353
|
+
|
|
354
|
+
This function determines the optimal transformation to apply to `wa` by
|
|
355
|
+
minimizing the averaged (over time) L² norm (over the sphere) of the
|
|
356
|
+
difference of the waveforms.
|
|
357
|
+
|
|
358
|
+
The integral is taken from time `t1` to `t2`.
|
|
359
|
+
|
|
360
|
+
Note that the input waveforms are assumed to be initially aligned at least
|
|
361
|
+
well enough that:
|
|
362
|
+
|
|
363
|
+
1) the time span from `t1` to `t2` in the two waveforms will overlap at
|
|
364
|
+
least slightly after the second waveform is shifted in time; and
|
|
365
|
+
2) waveform `wb` contains all the times corresponding to `t1` to `t2` in
|
|
366
|
+
waveform `wa`.
|
|
367
|
+
|
|
368
|
+
The first of these can usually be assured by simply aligning the peaks prior
|
|
369
|
+
to calling this function:
|
|
370
|
+
|
|
371
|
+
wa.t -= wa.max_norm_time() - wb.max_norm_time()
|
|
372
|
+
|
|
373
|
+
The second assumption will be satisfied as long as `t1` is not too close to
|
|
374
|
+
the beginning of `wb` and `t2` is not too close to the end.
|
|
375
|
+
|
|
376
|
+
Parameters
|
|
377
|
+
----------
|
|
378
|
+
wa : WaveformModes
|
|
379
|
+
wb : WaveformModes
|
|
380
|
+
Waveforms to be aligned
|
|
381
|
+
t1 : float
|
|
382
|
+
t2 : float
|
|
383
|
+
Beginning and end of integration interval
|
|
384
|
+
n_brute_force_δt : int, optional
|
|
385
|
+
Number of evenly spaced δt values between (t1-t2) and (t2-t1) to sample
|
|
386
|
+
for the initial guess. By default, this is 1,000. If this is too small,
|
|
387
|
+
an incorrect local minimum may be found.
|
|
388
|
+
n_brute_force_δϕ : int, optional
|
|
389
|
+
Number of evenly spaced angles about the angular-velocity axis to sample
|
|
390
|
+
for the initial guess. By default, this is `2 * (2 * ell_max + 1)`.
|
|
391
|
+
max_δt : float, optional
|
|
392
|
+
Max δt to allow for when choosing the initial guess.
|
|
393
|
+
include_modes: list, optional
|
|
394
|
+
A list containing the (ell, m) modes to be included in the L² norm.
|
|
395
|
+
align2d_first : bool, optional
|
|
396
|
+
Do a 2d align first for an initial guess, with no SO(3) initial guess
|
|
397
|
+
(besides the phase returned by the 2d solve)
|
|
398
|
+
nprocs: int, optional
|
|
399
|
+
Number of cpus to use. Default is maximum number. If -1 is provided,
|
|
400
|
+
then no multiprocessing is performed.
|
|
401
|
+
|
|
402
|
+
Returns
|
|
403
|
+
-------
|
|
404
|
+
error: float
|
|
405
|
+
Cost of scipy.optimize.least_squares. This is 0.5 ||wa - wb||² / ||wb||²
|
|
406
|
+
wa_prime: WaveformModes
|
|
407
|
+
Resulting waveform after transforming `wa` using `optimum`
|
|
408
|
+
optimum: OptimizeResult
|
|
409
|
+
Result of scipy.optimize.least_squares
|
|
410
|
+
|
|
411
|
+
Notes
|
|
412
|
+
-----
|
|
413
|
+
Choosing the time interval is usually the most difficult choice to make when
|
|
414
|
+
aligning waveforms. Assuming you want to align during inspiral, the times
|
|
415
|
+
must span sufficiently long that the waveforms' norm (equivalently, orbital
|
|
416
|
+
frequency changes) significantly from `t1` to `t2`. This means that you
|
|
417
|
+
cannot always rely on a specific number of orbits, for example. Also note
|
|
418
|
+
that neither number should be too close to the beginning or end of either
|
|
419
|
+
waveform, to provide some "wiggle room".
|
|
420
|
+
|
|
421
|
+
"""
|
|
422
|
+
from scipy.interpolate import CubicSpline
|
|
423
|
+
from scipy.optimize import least_squares
|
|
424
|
+
from .. import WaveformModes
|
|
425
|
+
|
|
426
|
+
if wa.spin_weight != wb.spin_weight:
|
|
427
|
+
raise ValueError(f"Waveform spin weights do not match: {wa.spin_weight=} != {wb.spin_weight=}")
|
|
428
|
+
spin_weight = wa.spin_weight
|
|
429
|
+
ell_min = max(wa.ell_min, wb.ell_min)
|
|
430
|
+
ell_max = min(wa.ell_max, wb.ell_max)
|
|
431
|
+
|
|
432
|
+
wa_orig = wa
|
|
433
|
+
wa = wa.copy()
|
|
434
|
+
wb = wb.copy()
|
|
435
|
+
|
|
436
|
+
# Check that (t1, t2) makes sense and is actually contained in both waveforms
|
|
437
|
+
if t2 <= t1:
|
|
438
|
+
raise ValueError(f"(t1,t2)=({t1}, {t2}) is out of order")
|
|
439
|
+
if wa.t[0] > t1 or wa.t[-1] < t2:
|
|
440
|
+
raise ValueError(f"(t1,t2)=({t1}, {t2}) not contained in wa.t, which spans ({wa.t[0]}, {wa.t[-1]})")
|
|
441
|
+
if wb.t[0] > t1 or wb.t[-1] < t2:
|
|
442
|
+
raise ValueError(f"(t1,t2)=({t1}, {t2}) not contained in wb.t, which spans ({wb.t[0]}, {wb.t[-1]})")
|
|
443
|
+
|
|
444
|
+
# Figure out time offsets to try
|
|
445
|
+
δt_lower = max(-max_δt, max(t1 - t2, t2 - wa.t[-1]))
|
|
446
|
+
δt_upper = min(max_δt, min(t2 - t1, t1 - wa.t[0]))
|
|
447
|
+
|
|
448
|
+
t_reference = wb.t[np.argmin(abs(wb.t - t1)) : np.argmin(abs(wb.t - t2)) + 1]
|
|
449
|
+
|
|
450
|
+
if not align2d_first:
|
|
451
|
+
# Get time initial guess
|
|
452
|
+
# Negative sign because align1d aligns wb to wa
|
|
453
|
+
δt_IG = -align1d(wa, wb, t1, t2)
|
|
454
|
+
|
|
455
|
+
wa_interp = wa.interpolate(t_reference + δt_IG)
|
|
456
|
+
wb_interp = wb.interpolate(t_reference)
|
|
457
|
+
|
|
458
|
+
# Get rotor initial guess
|
|
459
|
+
omegaa = wa_interp.angular_velocity
|
|
460
|
+
omegab = wb_interp.angular_velocity
|
|
461
|
+
R_IG = quaternionic.align(omegaa, omegab)
|
|
462
|
+
else:
|
|
463
|
+
_, _, res = align2d(
|
|
464
|
+
wa, wb, t1, t2, max_δt=50,
|
|
465
|
+
n_brute_force_δt=n_brute_force_δt, nprocs=nprocs
|
|
466
|
+
)
|
|
467
|
+
δt_IG = res.x[0]
|
|
468
|
+
|
|
469
|
+
wa_interp = wa.interpolate(t_reference + δt_IG)
|
|
470
|
+
wb_interp = wb.interpolate(t_reference)
|
|
471
|
+
|
|
472
|
+
# Get rotor initial guess
|
|
473
|
+
omegaa = wa_interp.angular_velocity
|
|
474
|
+
omegab = wb_interp.angular_velocity
|
|
475
|
+
R_IG = quaternionic.align(omegaa, omegab)
|
|
476
|
+
|
|
477
|
+
R_IG = quaternionic.array([0, 0, 0, res.x[1] / 2]) * R_IG
|
|
478
|
+
|
|
479
|
+
# Brute force over R_IG * exp(theta * z / 2) with δt_IG
|
|
480
|
+
n_brute_force_δϕ = n_brute_force_δϕ or 2 * (2 * ell_max + 1)
|
|
481
|
+
δt_δso3_brute_force = [
|
|
482
|
+
[δt_IG, *np.log(R_IG * np.exp(quaternionic.array([0, 0, 0, angle / 2]))).vector]
|
|
483
|
+
for angle in np.linspace(0, 2 * np.pi, num=n_brute_force_δϕ, endpoint=False)
|
|
484
|
+
]
|
|
485
|
+
|
|
486
|
+
# Remove certain modes, if requested
|
|
487
|
+
if include_modes is not None:
|
|
488
|
+
for L in range(ell_min, ell_max + 1):
|
|
489
|
+
for M in range(-L, L + 1):
|
|
490
|
+
if not (L, M) in include_modes:
|
|
491
|
+
wa.data[:, wa.index(L, M)] *= 0
|
|
492
|
+
wb.data[:, wb.index(L, M)] *= 0
|
|
493
|
+
|
|
494
|
+
# Define the cost function
|
|
495
|
+
modes_A = CubicSpline(wa.t, wa[:, wa.index(ell_min, -ell_min) : wa.index(ell_max, ell_max)+1].data)
|
|
496
|
+
modes_B = CubicSpline(wb.t, wb[:, wb.index(ell_min, -ell_min) : wb.index(ell_max, ell_max)+1].data)(t_reference)
|
|
497
|
+
|
|
498
|
+
normalization = trapezoid(
|
|
499
|
+
CubicSpline(
|
|
500
|
+
wb.t,
|
|
501
|
+
wb[:, wb.index(ell_min, -ell_min) : wb.index(ell_max, ell_max)+1].norm ** 2
|
|
502
|
+
)(t_reference),
|
|
503
|
+
t_reference,
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
# Optimize by brute force with multiprocessing
|
|
507
|
+
cost_wrapper = partial(_cost4d, args=[modes_A, modes_B, t_reference, normalization])
|
|
508
|
+
|
|
509
|
+
if nprocs != -1:
|
|
510
|
+
if nprocs is None:
|
|
511
|
+
nprocs = mp.cpu_count()
|
|
512
|
+
pool = mp.Pool(processes=nprocs)
|
|
513
|
+
cost_brute_force = pool.map(cost_wrapper, δt_δso3_brute_force)
|
|
514
|
+
pool.close()
|
|
515
|
+
pool.join()
|
|
516
|
+
else:
|
|
517
|
+
cost_brute_force = [
|
|
518
|
+
cost_wrapper(δt_δso3_brute_force_item)
|
|
519
|
+
for δt_δso3_brute_force_item in δt_δso3_brute_force
|
|
520
|
+
]
|
|
521
|
+
|
|
522
|
+
δt_δso3 = δt_δso3_brute_force[np.argmin(cost_brute_force)]
|
|
523
|
+
|
|
524
|
+
# Optimize explicitly
|
|
525
|
+
optimum = least_squares(
|
|
526
|
+
cost_wrapper,
|
|
527
|
+
δt_δso3,
|
|
528
|
+
bounds=[(δt_lower, -np.pi/2, -np.pi/2, -np.pi/2), (δt_upper, np.pi/2, np.pi/2, np.pi/2)],
|
|
529
|
+
max_nfev=50000,
|
|
530
|
+
)
|
|
531
|
+
δt = optimum.x[0]
|
|
532
|
+
δSO3 = np.exp(quaternionic.array.from_vector_part(optimum.x[1:]))
|
|
533
|
+
|
|
534
|
+
wa_prime = WaveformModes(
|
|
535
|
+
input_array=(wa_orig[:, wa_orig.index(ell_min, -ell_min) : wa_orig.index(ell_max, ell_max)+1].data),
|
|
536
|
+
time=wa_orig.t - δt,
|
|
537
|
+
time_axis=0,
|
|
538
|
+
modes_axis=1,
|
|
539
|
+
ell_min=ell_min,
|
|
540
|
+
ell_max=ell_max,
|
|
541
|
+
spin_weight=spin_weight,
|
|
542
|
+
)
|
|
543
|
+
wa_prime = wa_prime.rotate(δSO3)
|
|
544
|
+
|
|
545
|
+
return optimum.cost, wa_prime, optimum
|
sxs-2024.0.27/sxs/__version__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "2024.0.27"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|