sxs 2025.0.2__py3-none-any.whl → 2025.0.4__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/__version__.py +1 -1
- sxs/handlers.py +4 -2
- sxs/simulations/__init__.py +1 -0
- sxs/simulations/analyze.py +276 -0
- sxs/simulations/simulation.py +95 -21
- sxs/waveforms/__init__.py +1 -1
- sxs/waveforms/alignment.py +379 -55
- sxs/waveforms/format_handlers/lvc.py +3 -0
- sxs/waveforms/memory.py +11 -41
- sxs/waveforms/norms.py +270 -0
- sxs/waveforms/waveform_modes.py +84 -0
- sxs/waveforms/waveform_mts.py +30 -0
- {sxs-2025.0.2.dist-info → sxs-2025.0.4.dist-info}/METADATA +3 -2
- {sxs-2025.0.2.dist-info → sxs-2025.0.4.dist-info}/RECORD +16 -13
- {sxs-2025.0.2.dist-info → sxs-2025.0.4.dist-info}/WHEEL +0 -0
- {sxs-2025.0.2.dist-info → sxs-2025.0.4.dist-info}/licenses/LICENSE +0 -0
sxs/waveforms/alignment.py
CHANGED
|
@@ -7,6 +7,8 @@ from scipy.integrate import trapezoid
|
|
|
7
7
|
import multiprocessing as mp
|
|
8
8
|
from functools import partial
|
|
9
9
|
|
|
10
|
+
from .norms import L2_difference
|
|
11
|
+
|
|
10
12
|
|
|
11
13
|
def align1d(wa, wb, t1, t2, n_brute_force=None):
|
|
12
14
|
"""Align waveforms by shifting in time
|
|
@@ -132,7 +134,18 @@ def _cost2d(δt_δϕ, args):
|
|
|
132
134
|
return np.sqrt(diff / normalization)
|
|
133
135
|
|
|
134
136
|
|
|
135
|
-
def align2d(
|
|
137
|
+
def align2d(
|
|
138
|
+
wa,
|
|
139
|
+
wb,
|
|
140
|
+
t1,
|
|
141
|
+
t2,
|
|
142
|
+
n_brute_force_δt=None,
|
|
143
|
+
n_brute_force_δϕ=None,
|
|
144
|
+
max_δt=np.inf,
|
|
145
|
+
use_δΨ=False,
|
|
146
|
+
include_modes=None,
|
|
147
|
+
nprocs=None,
|
|
148
|
+
):
|
|
136
149
|
"""Align waveforms by shifting in time and phase
|
|
137
150
|
|
|
138
151
|
This function determines the optimal time and phase offset to apply to `wa` by
|
|
@@ -172,11 +185,7 @@ def align2d(wa, wb, t1, t2, n_brute_force_δt=None, n_brute_force_δϕ=5, max_δ
|
|
|
172
185
|
too small, an incorrect local minimum may be found.
|
|
173
186
|
n_brute_force_δϕ : int, optional
|
|
174
187
|
Number of evenly spaced δϕ values between 0 and 2π to sample
|
|
175
|
-
for the initial guess. By default, this is
|
|
176
|
-
`None` is provided, then this will be 2 * ell_max + 1. This option
|
|
177
|
-
is not the default because, even though it is formally the right
|
|
178
|
-
thing to do, it takes much longer to produce the same result
|
|
179
|
-
as just using 5, which is much faster.
|
|
188
|
+
for the initial guess. By default, this is 2 * ell_max + 1.
|
|
180
189
|
max_δt : float, optional
|
|
181
190
|
Max δt to allow for when choosing the initial guess.
|
|
182
191
|
use_δΨ : float, optional
|
|
@@ -227,7 +236,7 @@ def align2d(wa, wb, t1, t2, n_brute_force_δt=None, n_brute_force_δϕ=5, max_δ
|
|
|
227
236
|
# Figure out time offsets to try
|
|
228
237
|
δt_lower = max(-max_δt, max(t1 - t2, t2 - wa.t[-1]))
|
|
229
238
|
δt_upper = min(max_δt, min(t2 - t1, t1 - wa.t[0]))
|
|
230
|
-
|
|
239
|
+
|
|
231
240
|
# We'll start by brute forcing, sampling time offsets evenly at as many
|
|
232
241
|
# points as there are time steps in (t1,t2) in the input waveforms
|
|
233
242
|
if n_brute_force_δt is None:
|
|
@@ -299,7 +308,7 @@ def align2d(wa, wb, t1, t2, n_brute_force_δt=None, n_brute_force_δϕ=5, max_δ
|
|
|
299
308
|
modes_axis=1,
|
|
300
309
|
ell_min=2,
|
|
301
310
|
ell_max=ell_max,
|
|
302
|
-
spin_weight
|
|
311
|
+
spin_weight=wa.spin_weight,
|
|
303
312
|
)
|
|
304
313
|
wa_primes.append(wa_prime)
|
|
305
314
|
|
|
@@ -343,7 +352,7 @@ def align4d(
|
|
|
343
352
|
wb,
|
|
344
353
|
t1,
|
|
345
354
|
t2,
|
|
346
|
-
n_brute_force_δt=
|
|
355
|
+
n_brute_force_δt=None,
|
|
347
356
|
n_brute_force_δϕ=None,
|
|
348
357
|
max_δt=np.inf,
|
|
349
358
|
include_modes=None,
|
|
@@ -352,26 +361,26 @@ def align4d(
|
|
|
352
361
|
):
|
|
353
362
|
"""Align waveforms by optimizing over a time translation and an SO(3) rotation.
|
|
354
363
|
|
|
355
|
-
This function determines the optimal transformation to apply to `wa` by
|
|
356
|
-
minimizing the averaged (over time) L² norm (over the sphere) of the
|
|
364
|
+
This function determines the optimal transformation to apply to `wa` by
|
|
365
|
+
minimizing the averaged (over time) L² norm (over the sphere) of the
|
|
357
366
|
difference of the waveforms.
|
|
358
367
|
|
|
359
368
|
The integral is taken from time `t1` to `t2`.
|
|
360
369
|
|
|
361
|
-
Note that the input waveforms are assumed to be initially aligned at least
|
|
370
|
+
Note that the input waveforms are assumed to be initially aligned at least
|
|
362
371
|
well enough that:
|
|
363
372
|
|
|
364
|
-
1) the time span from `t1` to `t2` in the two waveforms will overlap at
|
|
373
|
+
1) the time span from `t1` to `t2` in the two waveforms will overlap at
|
|
365
374
|
least slightly after the second waveform is shifted in time; and
|
|
366
|
-
2) waveform `wb` contains all the times corresponding to `t1` to `t2` in
|
|
375
|
+
2) waveform `wb` contains all the times corresponding to `t1` to `t2` in
|
|
367
376
|
waveform `wa`.
|
|
368
377
|
|
|
369
|
-
The first of these can usually be assured by simply aligning the peaks prior
|
|
378
|
+
The first of these can usually be assured by simply aligning the peaks prior
|
|
370
379
|
to calling this function:
|
|
371
380
|
|
|
372
381
|
wa.t -= wa.max_norm_time() - wb.max_norm_time()
|
|
373
382
|
|
|
374
|
-
The second assumption will be satisfied as long as `t1` is not too close to
|
|
383
|
+
The second assumption will be satisfied as long as `t1` is not too close to
|
|
375
384
|
the beginning of `wb` and `t2` is not too close to the end.
|
|
376
385
|
|
|
377
386
|
Parameters
|
|
@@ -383,21 +392,21 @@ def align4d(
|
|
|
383
392
|
t2 : float
|
|
384
393
|
Beginning and end of integration interval
|
|
385
394
|
n_brute_force_δt : int, optional
|
|
386
|
-
Number of evenly spaced δt values between (t1-t2) and (t2-t1) to sample
|
|
387
|
-
for the initial guess. By default, this is 1,000. If this is too small,
|
|
395
|
+
Number of evenly spaced δt values between (t1-t2) and (t2-t1) to sample
|
|
396
|
+
for the initial guess. By default, this is 1,000. If this is too small,
|
|
388
397
|
an incorrect local minimum may be found.
|
|
389
398
|
n_brute_force_δϕ : int, optional
|
|
390
|
-
Number of evenly spaced angles about the angular-velocity axis to sample
|
|
399
|
+
Number of evenly spaced angles about the angular-velocity axis to sample
|
|
391
400
|
for the initial guess. By default, this is `2 * (2 * ell_max + 1)`.
|
|
392
401
|
max_δt : float, optional
|
|
393
402
|
Max δt to allow for when choosing the initial guess.
|
|
394
403
|
include_modes: list, optional
|
|
395
404
|
A list containing the (ell, m) modes to be included in the L² norm.
|
|
396
405
|
align2d_first : bool, optional
|
|
397
|
-
Do a 2d align first for an initial guess, with no SO(3) initial guess
|
|
406
|
+
Do a 2d align first for an initial guess, with no SO(3) initial guess
|
|
398
407
|
(besides the phase returned by the 2d solve)
|
|
399
408
|
nprocs: int, optional
|
|
400
|
-
Number of cpus to use. Default is maximum number. If -1 is provided,
|
|
409
|
+
Number of cpus to use. Default is maximum number. If -1 is provided,
|
|
401
410
|
then no multiprocessing is performed.
|
|
402
411
|
|
|
403
412
|
Returns
|
|
@@ -411,12 +420,12 @@ def align4d(
|
|
|
411
420
|
|
|
412
421
|
Notes
|
|
413
422
|
-----
|
|
414
|
-
Choosing the time interval is usually the most difficult choice to make when
|
|
415
|
-
aligning waveforms. Assuming you want to align during inspiral, the times
|
|
416
|
-
must span sufficiently long that the waveforms' norm (equivalently, orbital
|
|
417
|
-
frequency changes) significantly from `t1` to `t2`. This means that you
|
|
418
|
-
cannot always rely on a specific number of orbits, for example. Also note
|
|
419
|
-
that neither number should be too close to the beginning or end of either
|
|
423
|
+
Choosing the time interval is usually the most difficult choice to make when
|
|
424
|
+
aligning waveforms. Assuming you want to align during inspiral, the times
|
|
425
|
+
must span sufficiently long that the waveforms' norm (equivalently, orbital
|
|
426
|
+
frequency changes) significantly from `t1` to `t2`. This means that you
|
|
427
|
+
cannot always rely on a specific number of orbits, for example. Also note
|
|
428
|
+
that neither number should be too close to the beginning or end of either
|
|
420
429
|
waveform, to provide some "wiggle room".
|
|
421
430
|
|
|
422
431
|
"""
|
|
@@ -447,24 +456,21 @@ def align4d(
|
|
|
447
456
|
δt_upper = min(max_δt, min(t2 - t1, t1 - wa.t[0]))
|
|
448
457
|
|
|
449
458
|
t_reference = wb.t[np.argmin(abs(wb.t - t1)) : np.argmin(abs(wb.t - t2)) + 1]
|
|
450
|
-
|
|
459
|
+
|
|
451
460
|
if not align2d_first:
|
|
452
461
|
# Get time initial guess
|
|
453
462
|
# Negative sign because align1d aligns wb to wa
|
|
454
463
|
δt_IG = -align1d(wa, wb, t1, t2)
|
|
455
|
-
|
|
464
|
+
|
|
456
465
|
wa_interp = wa.interpolate(t_reference + δt_IG)
|
|
457
466
|
wb_interp = wb.interpolate(t_reference)
|
|
458
|
-
|
|
467
|
+
|
|
459
468
|
# Get rotor initial guess
|
|
460
469
|
omegaa = wa_interp.angular_velocity
|
|
461
470
|
omegab = wb_interp.angular_velocity
|
|
462
471
|
R_IG = quaternionic.align(omegaa, omegab)
|
|
463
472
|
else:
|
|
464
|
-
_, _, res = align2d(
|
|
465
|
-
wa, wb, t1, t2, max_δt=50,
|
|
466
|
-
n_brute_force_δt=n_brute_force_δt, nprocs=nprocs
|
|
467
|
-
)
|
|
473
|
+
_, _, res = align2d(wa, wb, t1, t2, max_δt=max_δt, n_brute_force_δt=n_brute_force_δt, nprocs=nprocs)
|
|
468
474
|
δt_IG = res.x[0]
|
|
469
475
|
|
|
470
476
|
wa_interp = wa.interpolate(t_reference + δt_IG)
|
|
@@ -474,18 +480,13 @@ def align4d(
|
|
|
474
480
|
omegaa = wa_interp.angular_velocity
|
|
475
481
|
omegab = wb_interp.angular_velocity
|
|
476
482
|
R_IG = quaternionic.align(omegaa, omegab)
|
|
477
|
-
|
|
483
|
+
|
|
478
484
|
R_IG = quaternionic.array([0, 0, 0, res.x[1] / 2]) * R_IG
|
|
479
485
|
|
|
480
486
|
# Brute force over R_IG * exp(theta * z / 2) with δt_IG
|
|
481
487
|
n_brute_force_δϕ = n_brute_force_δϕ or 2 * (2 * ell_max + 1)
|
|
482
488
|
δt_δso3_brute_force = [
|
|
483
|
-
[
|
|
484
|
-
δt_IG,
|
|
485
|
-
*np.log(
|
|
486
|
-
(R_IG * np.exp(quaternionic.array([0, 0, 0, angle / 2]))).canonicalized
|
|
487
|
-
).vector
|
|
488
|
-
]
|
|
489
|
+
[δt_IG, *np.log((R_IG * np.exp(quaternionic.array([0, 0, 0, angle / 2]))).canonicalized).vector]
|
|
489
490
|
for angle in np.linspace(-np.pi, np.pi, num=n_brute_force_δϕ, endpoint=False)
|
|
490
491
|
]
|
|
491
492
|
|
|
@@ -498,14 +499,11 @@ def align4d(
|
|
|
498
499
|
wb.data[:, wb.index(L, M)] *= 0
|
|
499
500
|
|
|
500
501
|
# Define the cost function
|
|
501
|
-
modes_A = CubicSpline(wa.t, wa[:, wa.index(ell_min, -ell_min) : wa.index(ell_max, ell_max)+1].data)
|
|
502
|
-
modes_B = CubicSpline(wb.t, wb[:, wb.index(ell_min, -ell_min) : wb.index(ell_max, ell_max)+1].data)(t_reference)
|
|
502
|
+
modes_A = CubicSpline(wa.t, wa[:, wa.index(ell_min, -ell_min) : wa.index(ell_max, ell_max) + 1].data)
|
|
503
|
+
modes_B = CubicSpline(wb.t, wb[:, wb.index(ell_min, -ell_min) : wb.index(ell_max, ell_max) + 1].data)(t_reference)
|
|
503
504
|
|
|
504
505
|
normalization = trapezoid(
|
|
505
|
-
CubicSpline(
|
|
506
|
-
wb.t,
|
|
507
|
-
wb[:, wb.index(ell_min, -ell_min) : wb.index(ell_max, ell_max)+1].norm ** 2
|
|
508
|
-
)(t_reference),
|
|
506
|
+
CubicSpline(wb.t, wb[:, wb.index(ell_min, -ell_min) : wb.index(ell_max, ell_max) + 1].norm ** 2)(t_reference),
|
|
509
507
|
t_reference,
|
|
510
508
|
)
|
|
511
509
|
|
|
@@ -520,25 +518,22 @@ def align4d(
|
|
|
520
518
|
pool.close()
|
|
521
519
|
pool.join()
|
|
522
520
|
else:
|
|
523
|
-
cost_brute_force = [
|
|
524
|
-
cost_wrapper(δt_δso3_brute_force_item)
|
|
525
|
-
for δt_δso3_brute_force_item in δt_δso3_brute_force
|
|
526
|
-
]
|
|
521
|
+
cost_brute_force = [cost_wrapper(δt_δso3_brute_force_item) for δt_δso3_brute_force_item in δt_δso3_brute_force]
|
|
527
522
|
|
|
528
523
|
δt_δso3 = δt_δso3_brute_force[np.argmin(cost_brute_force)]
|
|
529
|
-
|
|
524
|
+
|
|
530
525
|
# Optimize explicitly
|
|
531
526
|
optimum = least_squares(
|
|
532
527
|
cost_wrapper,
|
|
533
528
|
δt_δso3,
|
|
534
|
-
bounds=[(δt_lower, -np.pi/2, -np.pi/2, -np.pi/2), (δt_upper, np.pi/2, np.pi/2, np.pi/2)],
|
|
529
|
+
bounds=[(δt_lower, -np.pi / 2, -np.pi / 2, -np.pi / 2), (δt_upper, np.pi / 2, np.pi / 2, np.pi / 2)],
|
|
535
530
|
max_nfev=50000,
|
|
536
531
|
)
|
|
537
532
|
δt = optimum.x[0]
|
|
538
533
|
δSpin3 = np.exp(quaternionic.array.from_vector_part(optimum.x[1:]))
|
|
539
|
-
|
|
534
|
+
|
|
540
535
|
wa_prime = WaveformModes(
|
|
541
|
-
input_array=(wa_orig[:, wa_orig.index(ell_min, -ell_min) : wa_orig.index(ell_max, ell_max)+1].data),
|
|
536
|
+
input_array=(wa_orig[:, wa_orig.index(ell_min, -ell_min) : wa_orig.index(ell_max, ell_max) + 1].data),
|
|
542
537
|
time=wa_orig.t - δt,
|
|
543
538
|
time_axis=0,
|
|
544
539
|
modes_axis=1,
|
|
@@ -549,3 +544,332 @@ def align4d(
|
|
|
549
544
|
wa_prime = wa_prime.rotate(δSpin3)
|
|
550
545
|
|
|
551
546
|
return optimum.cost, wa_prime, optimum
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
def map_waveform_to_canonical_frame(wa, t_ref):
|
|
550
|
+
"""Map waveform to canonical frame at t_ref.
|
|
551
|
+
|
|
552
|
+
This amounts to mapping the peak time to zero,
|
|
553
|
+
aligning the angular velocity with the z axis at t_ref,
|
|
554
|
+
fixing the phase of (2,2) to be zero at t_ref, and
|
|
555
|
+
making Re[(2,1)] > 0 at t_ref.
|
|
556
|
+
|
|
557
|
+
This frame alignment is similar to that performed in
|
|
558
|
+
waveforms.format_handlers.lvc.to_lvc_conventions.
|
|
559
|
+
|
|
560
|
+
Parameters
|
|
561
|
+
----------
|
|
562
|
+
wa : WaveformModes
|
|
563
|
+
t_ref : float
|
|
564
|
+
Reference time, relative to peak strain.
|
|
565
|
+
|
|
566
|
+
Returns
|
|
567
|
+
-------
|
|
568
|
+
wa_prime: WaveformModes
|
|
569
|
+
wa waveform aligned to wb.
|
|
570
|
+
transformation: ndarray
|
|
571
|
+
Transformation to map wa to wa_prime.
|
|
572
|
+
"""
|
|
573
|
+
δt = 0
|
|
574
|
+
δSpin3 = quaternionic.array([0, 0, 0, 1])
|
|
575
|
+
|
|
576
|
+
# Fix t so that peaks agree
|
|
577
|
+
try:
|
|
578
|
+
δt = wa.max_norm_time(interpolate=True)
|
|
579
|
+
except:
|
|
580
|
+
δt = wa.max_norm_time()
|
|
581
|
+
|
|
582
|
+
idx_ref = np.argmin(abs(wa.t - (δt + t_ref)))
|
|
583
|
+
|
|
584
|
+
# Fix angular velocity to be aligned with z
|
|
585
|
+
omegaa = wa.angular_velocity[idx_ref]
|
|
586
|
+
δSpin3 = quaternionic.align(np.array([omegaa]), np.array([quaternionic.z.vector]))
|
|
587
|
+
|
|
588
|
+
wa_rot = wa.rotate(δSpin3)
|
|
589
|
+
|
|
590
|
+
# Fix the phase of (2,2) to be zero
|
|
591
|
+
dphase = (-np.unwrap(np.angle(wa_rot.data[:, wa.index(2, 2)])) / 2)[idx_ref]
|
|
592
|
+
if (wa_rot.data[idx_ref, wa.index(2, 1)] * np.exp(1j * dphase)).real < 0:
|
|
593
|
+
dphase += np.pi
|
|
594
|
+
|
|
595
|
+
δSpin3 = δSpin3 * np.exp(quaternionic.array([0, 0, 0, dphase / 2]))
|
|
596
|
+
|
|
597
|
+
wa_prime = wa.copy()
|
|
598
|
+
wa_prime.t = wa_prime.t - δt
|
|
599
|
+
wa_prime = wa_prime.rotate(δSpin3)
|
|
600
|
+
|
|
601
|
+
return wa_prime, [δt, δSpin3]
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
def align_waveforms(
|
|
605
|
+
wa,
|
|
606
|
+
wb,
|
|
607
|
+
t1=0,
|
|
608
|
+
t2=None,
|
|
609
|
+
alignment_method="independent alignment",
|
|
610
|
+
use_initial_guess=True,
|
|
611
|
+
t_ref=None,
|
|
612
|
+
n_brute_force_δt=None,
|
|
613
|
+
n_brute_force_δϕ=None,
|
|
614
|
+
max_δt=np.inf,
|
|
615
|
+
omega_tol=0.1,
|
|
616
|
+
nprocs=None,
|
|
617
|
+
):
|
|
618
|
+
"""Align waveforms by fixing the frame of each waveform at some
|
|
619
|
+
reference time or by performing an alignment optimization (1d, 2d, or 4d).
|
|
620
|
+
|
|
621
|
+
alignment_method determines what alignment is performed. If 'independent alignment'
|
|
622
|
+
then each simulation's frame is fixed at t_ref; if '1d', '2d', or '4d', then the
|
|
623
|
+
corresponding optimization is performed over [t1, t2].
|
|
624
|
+
|
|
625
|
+
Parameters
|
|
626
|
+
----------
|
|
627
|
+
wa : WaveformModes
|
|
628
|
+
wb : WaveformModes
|
|
629
|
+
WaveformModes to be aligned
|
|
630
|
+
t1 : float
|
|
631
|
+
Beginning of integration interval.
|
|
632
|
+
Default is 0.
|
|
633
|
+
t2 : float
|
|
634
|
+
End of integration interval.
|
|
635
|
+
Default is 60% of the ringdown, or 100M before peak if there is no merger.
|
|
636
|
+
alignment_method : str
|
|
637
|
+
Alignment method to use:
|
|
638
|
+
- "independent alignment" aligns each simulation to some frame at t_ref;
|
|
639
|
+
- time is set to zero at the peak;
|
|
640
|
+
- angular velocity vector is aligned with z a t_ref;
|
|
641
|
+
- phase of (2,2) is set to zero at t_ref;
|
|
642
|
+
- real part of the (2,1) mode is made positive at t_ref (fixes π freedom);
|
|
643
|
+
- "1d" performs a 1d optimization over time translations;
|
|
644
|
+
- "2d" performs a 2d optimization over time translations and rotations about the z axis;
|
|
645
|
+
- "4d" performs a 4d optimization over time translations and SO(3) rotations.
|
|
646
|
+
use_initial_guess : bool
|
|
647
|
+
Whether or not to use "independent alignment" as an initial guess.
|
|
648
|
+
Default is True.
|
|
649
|
+
t_ref : float
|
|
650
|
+
Reference time, relative to peak strain, for independent alignment.
|
|
651
|
+
Default is 10% before peak.
|
|
652
|
+
n_brute_force_δt : int, optional
|
|
653
|
+
Number of evenly spaced δt values between (t1-t2) and (t2-t1) to sample
|
|
654
|
+
for the initial guess. By default, this is just the maximum number of
|
|
655
|
+
time steps in the range (t1, t2) in the input waveforms. If this is
|
|
656
|
+
too small, an incorrect local minimum may be found.
|
|
657
|
+
n_brute_force_δϕ : int, optional
|
|
658
|
+
Number of evenly spaced angles about the angular-velocity axis to sample
|
|
659
|
+
for the initial guess. By default, this is `2 * (2 * ell_max + 1)`.
|
|
660
|
+
max_δt : float, optional
|
|
661
|
+
Max δt to allow for when choosing the initial guess.
|
|
662
|
+
omega_tol: float, optional
|
|
663
|
+
Angular velocity magnitude tolerance to be used to fix rotation.
|
|
664
|
+
Default is 0.1
|
|
665
|
+
nprocs : int, optional
|
|
666
|
+
Number of cpus to use. Default is maximum number. If -1 is provided,
|
|
667
|
+
then no multiprocessing is performed.
|
|
668
|
+
|
|
669
|
+
Returns
|
|
670
|
+
-------
|
|
671
|
+
wa_prime : WaveformModes
|
|
672
|
+
wa waveform aligned to wb.
|
|
673
|
+
transformation : ndarray
|
|
674
|
+
Transformation ([dt, *SO(3) quaternion]) to map wa to wa_prime.
|
|
675
|
+
L2_norm : float
|
|
676
|
+
L² norm of normalized residual between wa_prime and wb over [t1, t2].
|
|
677
|
+
t1 : float
|
|
678
|
+
t2 : float
|
|
679
|
+
"""
|
|
680
|
+
wa_prime = wa.copy()
|
|
681
|
+
|
|
682
|
+
if t2 is None:
|
|
683
|
+
# Default to 60% of the post peak signal
|
|
684
|
+
try:
|
|
685
|
+
t2 = min(
|
|
686
|
+
wa.max_norm_time(interpolate=True) + 0.6 * (wa.t[-1] - wa.max_norm_time(interpolate=True)),
|
|
687
|
+
wb.max_norm_time(interpolate=True) + 0.6 * (wb.t[-1] - wb.max_norm_time(interpolate=True)),
|
|
688
|
+
)
|
|
689
|
+
except:
|
|
690
|
+
t2 = min(
|
|
691
|
+
wa.max_norm_time() + 0.6 * (wa.t[-1] - wa.max_norm_time()),
|
|
692
|
+
wb.max_norm_time() + 0.6 * (wb.t[-1] - wb.max_norm_time()),
|
|
693
|
+
)
|
|
694
|
+
|
|
695
|
+
# or 100M before peak signal (for cases with no merger)
|
|
696
|
+
if abs(t2) < 1e-2:
|
|
697
|
+
t2 -= 100
|
|
698
|
+
|
|
699
|
+
if t_ref is None:
|
|
700
|
+
try:
|
|
701
|
+
t_ref = -0.1 * (wb.max_norm_time(interpolate=True) - t1)
|
|
702
|
+
except:
|
|
703
|
+
t_ref = -0.1 * (wb.max_norm_time() - t1)
|
|
704
|
+
|
|
705
|
+
_, transformationa = map_waveform_to_canonical_frame(wa, t_ref)
|
|
706
|
+
_, transformationb = map_waveform_to_canonical_frame(wb, t_ref)
|
|
707
|
+
δt_IG = transformationa[0] - transformationb[0]
|
|
708
|
+
δSpin3_IG = transformationa[1] * transformationb[1].inverse
|
|
709
|
+
|
|
710
|
+
wa_prime_IG = wa.copy()
|
|
711
|
+
wa_prime_IG.t = wa_prime_IG.t - δt_IG
|
|
712
|
+
wa_prime_IG = wa_prime_IG.rotate(δSpin3_IG)
|
|
713
|
+
|
|
714
|
+
if alignment_method == "independent alignment":
|
|
715
|
+
δt = δt_IG
|
|
716
|
+
δSpin3 = δSpin3_IG
|
|
717
|
+
wa_prime = wa_prime_IG
|
|
718
|
+
elif not use_initial_guess:
|
|
719
|
+
δt_IG = 0
|
|
720
|
+
δSpin3_IG = quaternionic.one
|
|
721
|
+
wa_prime_IG = wa_prime.copy()
|
|
722
|
+
|
|
723
|
+
if alignment_method == "1d":
|
|
724
|
+
δt = δt_IG + -align1d(wa_prime_IG, wb, t1, t2, n_brute_force=n_brute_force_δt)
|
|
725
|
+
δSpin3 = δSpin3_IG
|
|
726
|
+
wa_prime.t = wa_prime.t - δt
|
|
727
|
+
elif alignment_method == "2d":
|
|
728
|
+
_, wa_prime, res = align2d(
|
|
729
|
+
wa_prime_IG,
|
|
730
|
+
wb,
|
|
731
|
+
t1,
|
|
732
|
+
t2,
|
|
733
|
+
n_brute_force_δt=n_brute_force_δt,
|
|
734
|
+
n_brute_force_δϕ=n_brute_force_δϕ,
|
|
735
|
+
max_δt=max_δt,
|
|
736
|
+
nprocs=nprocs,
|
|
737
|
+
)
|
|
738
|
+
δt = δt_IG + res.x[0]
|
|
739
|
+
δSpin3 = np.exp(quaternionic.array([0, 0, 0, res.x[1] / 2])) * δSpin3_IG
|
|
740
|
+
elif alignment_method == "4d":
|
|
741
|
+
_, wa_prime, res = align4d(
|
|
742
|
+
wa_prime_IG,
|
|
743
|
+
wb,
|
|
744
|
+
t1,
|
|
745
|
+
t2,
|
|
746
|
+
n_brute_force_δt=n_brute_force_δt,
|
|
747
|
+
n_brute_force_δϕ=n_brute_force_δϕ,
|
|
748
|
+
max_δt=max_δt,
|
|
749
|
+
nprocs=nprocs,
|
|
750
|
+
)
|
|
751
|
+
δt = δt_IG + res.x[0]
|
|
752
|
+
δSpin3 = np.exp(quaternionic.array.from_vector_part(res.x[1:])) * δSpin3_IG
|
|
753
|
+
|
|
754
|
+
δSpin3 = δSpin3.canonicalized
|
|
755
|
+
|
|
756
|
+
return wa_prime, np.array([δt, *δSpin3.ndarray]), L2_difference(wa_prime, wb, t1, t2), t1, t2
|
|
757
|
+
|
|
758
|
+
|
|
759
|
+
def align_simulations(
|
|
760
|
+
sima,
|
|
761
|
+
simb,
|
|
762
|
+
t1=None,
|
|
763
|
+
t2=None,
|
|
764
|
+
alignment_method="independent alignment",
|
|
765
|
+
use_initial_guess=True,
|
|
766
|
+
t_ref=None,
|
|
767
|
+
n_brute_force_δt=None,
|
|
768
|
+
n_brute_force_δϕ=None,
|
|
769
|
+
max_δt=np.inf,
|
|
770
|
+
omega_tol=0.1,
|
|
771
|
+
nprocs=None,
|
|
772
|
+
):
|
|
773
|
+
"""Align simulations by fixing the frame of each simulation at some
|
|
774
|
+
reference time or by performing an alignment optimization (1d, 2d, or 4d).
|
|
775
|
+
|
|
776
|
+
alignment_method determines what alignment is performed. If 'independent alignment'
|
|
777
|
+
then each simulation's frame is fixed at t_ref (if not provided, taken to be 10%
|
|
778
|
+
of the pre-merger phase before the peak of sima; if '1d', '2d', or '4d', then the
|
|
779
|
+
corresponding optimization is performed over [t1, t2].
|
|
780
|
+
|
|
781
|
+
Parameters
|
|
782
|
+
----------
|
|
783
|
+
sima : Simulation
|
|
784
|
+
simb : Simulation
|
|
785
|
+
Simulations to be aligned
|
|
786
|
+
t1 : float
|
|
787
|
+
Beginning of integration interval.
|
|
788
|
+
Default is the reference time of simb.
|
|
789
|
+
t2 : float
|
|
790
|
+
End of integration interval.
|
|
791
|
+
Default is 60% of the ringdown, or 100M before peak if there is no merger.
|
|
792
|
+
alignment_method : str
|
|
793
|
+
Alignment method to use;
|
|
794
|
+
- "independent alignment" aligns each simulation to some frame at t_ref;
|
|
795
|
+
- time is set to zero at the peak;
|
|
796
|
+
- angular velocity vector is aligned with z a t_ref;
|
|
797
|
+
- phase of (2,2) is set to zer at t_ref;
|
|
798
|
+
- real part of the (2,1) mode is made positive at t_ref (fixes π freedom);
|
|
799
|
+
- "1d" performs a 1d optimization over time translations;
|
|
800
|
+
- "2d" performs a 2d optimization over time translations and rotations about the z-axis;
|
|
801
|
+
- "4d" performs a 4d optimization over time translations and SO(3) rotations;
|
|
802
|
+
use_initial_guess : bool
|
|
803
|
+
Whether or not to use "independent alignment" as an initial guess.
|
|
804
|
+
Default is True.
|
|
805
|
+
t_ref : float
|
|
806
|
+
Reference time for independent alignment.
|
|
807
|
+
Default is 10% before peak.
|
|
808
|
+
n_brute_force_δt : int, optional
|
|
809
|
+
Number of evenly spaced δt values between (t1-t2) and (t2-t1) to sample
|
|
810
|
+
for the initial guess. By default, this is just the maximum number of
|
|
811
|
+
time steps in the range (t1, t2) in the input waveforms. If this is
|
|
812
|
+
too small, an incorrect local minimum may be found.
|
|
813
|
+
n_brute_force_δϕ : int, optional
|
|
814
|
+
Number of evenly spaced angles about the angular-velocity axis to sample
|
|
815
|
+
for the initial guess. By default, this is `2 * (2 * ell_max + 1)`.
|
|
816
|
+
max_δt : float, optional
|
|
817
|
+
Max δt to allow for when choosing the initial guess.
|
|
818
|
+
omega_tol: float, optional
|
|
819
|
+
Angular velocity magnitude tolerance to be used to fix rotation.
|
|
820
|
+
Default is 0.1
|
|
821
|
+
nprocs : int, optional
|
|
822
|
+
Number of cpus to use. Default is maximum number. If -1 is provided,
|
|
823
|
+
then no multiprocessing is performed.
|
|
824
|
+
|
|
825
|
+
Returns
|
|
826
|
+
-------
|
|
827
|
+
wa_prime : WaveformModes
|
|
828
|
+
Waveform sima.h aligned to Simulation simb.h.
|
|
829
|
+
transformation : ndarray
|
|
830
|
+
Transformation ([dt, *SO(3) quaternion]) to map sima.h to wa_prime.
|
|
831
|
+
L2_norm : float
|
|
832
|
+
L² norm of normalized residual between wa_prime and wb over [t1, t2].
|
|
833
|
+
t1 : float
|
|
834
|
+
t2 : float
|
|
835
|
+
"""
|
|
836
|
+
wa = sima.h.copy()
|
|
837
|
+
wb = simb.h.copy()
|
|
838
|
+
|
|
839
|
+
if t1 is None:
|
|
840
|
+
# Default to reference time
|
|
841
|
+
t1 = simb.metadata.reference_time
|
|
842
|
+
|
|
843
|
+
wa_prime, transformation, L2_norm, t1, t2 = align_waveforms(
|
|
844
|
+
sima.h,
|
|
845
|
+
simb.h,
|
|
846
|
+
t1,
|
|
847
|
+
t2,
|
|
848
|
+
alignment_method="independent alignment",
|
|
849
|
+
use_initial_guess=use_initial_guess,
|
|
850
|
+
t_ref=t_ref,
|
|
851
|
+
n_brute_force_δt=n_brute_force_δt,
|
|
852
|
+
n_brute_force_δϕ=n_brute_force_δϕ,
|
|
853
|
+
max_δt=max_δt,
|
|
854
|
+
omega_tol=omega_tol,
|
|
855
|
+
nprocs=nprocs,
|
|
856
|
+
)
|
|
857
|
+
|
|
858
|
+
if alignment_method == "independent_alignment":
|
|
859
|
+
return wa_prime, transformation, L2_norm, t1, t2
|
|
860
|
+
|
|
861
|
+
wa_prime, transformation, L2_norm, t1, t2 = align_waveforms(
|
|
862
|
+
sima.h,
|
|
863
|
+
simb.h,
|
|
864
|
+
t1,
|
|
865
|
+
t2,
|
|
866
|
+
alignment_method=alignment_method,
|
|
867
|
+
t_ref=t_ref,
|
|
868
|
+
n_brute_force_δt=n_brute_force_δt,
|
|
869
|
+
n_brute_force_δϕ=n_brute_force_δϕ,
|
|
870
|
+
max_δt=max_δt,
|
|
871
|
+
omega_tol=omega_tol,
|
|
872
|
+
nprocs=nprocs,
|
|
873
|
+
)
|
|
874
|
+
|
|
875
|
+
return wa_prime, transformation, L2_norm, t1, t2
|
|
@@ -43,6 +43,9 @@ def to_lvc_conventions(
|
|
|
43
43
|
of what this function does, the inputs, and outputs, see that
|
|
44
44
|
function's docstring.
|
|
45
45
|
|
|
46
|
+
The frame alignment performed in this function is similar
|
|
47
|
+
to that performed in sxs.waveforms.alignment.map_waveform_to_canonical_frame.
|
|
48
|
+
|
|
46
49
|
"""
|
|
47
50
|
if t_ref is None and f_ref is None:
|
|
48
51
|
raise ValueError("One of `t_ref` or `f_ref` must be specified")
|
sxs/waveforms/memory.py
CHANGED
|
@@ -13,37 +13,7 @@ h_with_memory = sxs.waveforms.memory.add_memory(h, integration_start_time=1000.0
|
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
15
|
import numpy as np
|
|
16
|
-
import
|
|
17
|
-
from . import WaveformModes
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class _ModesTimeSeries(WaveformModes):
|
|
21
|
-
|
|
22
|
-
@property
|
|
23
|
-
def s(self):
|
|
24
|
-
return self.spin_weight
|
|
25
|
-
|
|
26
|
-
@property
|
|
27
|
-
def u(self):
|
|
28
|
-
return self.time
|
|
29
|
-
|
|
30
|
-
from spherical.modes.algebra import (
|
|
31
|
-
conjugate, bar, _real_func, real, _imag_func, imag, norm,
|
|
32
|
-
add, subtract, multiply, divide
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
conj = conjugate
|
|
36
|
-
|
|
37
|
-
from spherical.modes.utilities import (
|
|
38
|
-
truncate_ell, _check_broadcasting
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
from spherical.modes.ufuncs import __array_ufunc__
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def MTS(*args, **kwargs):
|
|
45
|
-
kwargs.setdefault('multiplication_truncator', max)
|
|
46
|
-
return _ModesTimeSeries(*args, **kwargs)
|
|
16
|
+
from . import waveform_mts
|
|
47
17
|
|
|
48
18
|
|
|
49
19
|
def 𝔇(h_mts):
|
|
@@ -107,8 +77,8 @@ def 𝔇inverseLaplacianinverse(h_mts):
|
|
|
107
77
|
|
|
108
78
|
|
|
109
79
|
def mass_aspect(Psi2, h):
|
|
110
|
-
h = MTS(h)
|
|
111
|
-
Psi2 = MTS(Psi2)
|
|
80
|
+
h = waveform_mts.MTS(h)
|
|
81
|
+
Psi2 = waveform_mts.MTS(Psi2)
|
|
112
82
|
return - (Psi2 + 0.25 * h.dot * h.bar).re
|
|
113
83
|
|
|
114
84
|
|
|
@@ -131,8 +101,8 @@ def J_m(h, Psi2):
|
|
|
131
101
|
Bondi mass aspect contribution to the strain
|
|
132
102
|
|
|
133
103
|
"""
|
|
134
|
-
h = MTS(h)
|
|
135
|
-
Psi2 = MTS(Psi2)
|
|
104
|
+
h = waveform_mts.MTS(h)
|
|
105
|
+
Psi2 = waveform_mts.MTS(Psi2)
|
|
136
106
|
m = mass_aspect(Psi2, h)
|
|
137
107
|
J_m = 0.5 * 𝔇inverse(m).ethbar.ethbar
|
|
138
108
|
|
|
@@ -160,7 +130,7 @@ def J_E(h, integration_start_time=None):
|
|
|
160
130
|
|
|
161
131
|
"""
|
|
162
132
|
|
|
163
|
-
hdot = MTS(h).dot
|
|
133
|
+
hdot = waveform_mts.MTS(h).dot
|
|
164
134
|
|
|
165
135
|
J_ℰ = 0.5 * 𝔇inverse(0.25 * (hdot * hdot.bar).int).ethbar.ethbar
|
|
166
136
|
|
|
@@ -191,8 +161,8 @@ def J_Nhat(h, Psi2):
|
|
|
191
161
|
|
|
192
162
|
"""
|
|
193
163
|
|
|
194
|
-
h = MTS(h)
|
|
195
|
-
Psi2 = MTS(Psi2)
|
|
164
|
+
h = waveform_mts.MTS(h)
|
|
165
|
+
Psi2 = waveform_mts.MTS(Psi2)
|
|
196
166
|
|
|
197
167
|
# # Note that the contributions from the last two terms drop out as soon as we
|
|
198
168
|
# # take the imaginary part below.
|
|
@@ -228,7 +198,7 @@ def J_J(h):
|
|
|
228
198
|
|
|
229
199
|
"""
|
|
230
200
|
|
|
231
|
-
h = MTS(h)
|
|
201
|
+
h = waveform_mts.MTS(h)
|
|
232
202
|
hdot = h.dot
|
|
233
203
|
J_𝒥 = 0.5j * 𝔇inverseLaplacianinverse(
|
|
234
204
|
0.125 * (3 * h * hdot.bar.ethbar - 3 * hdot * h.bar.ethbar + hdot.bar * h.ethbar - h.bar * hdot.ethbar).eth.im
|
|
@@ -263,11 +233,11 @@ def add_memory(h, integration_start_time=None, psi4=None):
|
|
|
263
233
|
|
|
264
234
|
"""
|
|
265
235
|
h_memory_correction = J_E(h, integration_start_time=integration_start_time)
|
|
266
|
-
h_with_memory = WaveformModes(MTS(h) + h_memory_correction)
|
|
236
|
+
h_with_memory = WaveformModes(waveform_mts.MTS(h) + h_memory_correction)
|
|
267
237
|
h_with_memory.register_modification(add_memory, integration_start_time=integration_start_time)
|
|
268
238
|
if psi4 is None:
|
|
269
239
|
return h_with_memory
|
|
270
240
|
else:
|
|
271
|
-
psi4_with_memory = WaveformModes(MTS(psi4) - MTS(h_memory_correction).ddot)
|
|
241
|
+
psi4_with_memory = WaveformModes(waveform_mts.MTS(psi4) - waveform_mts.MTS(h_memory_correction).ddot)
|
|
272
242
|
psi4_with_memory.register_modification(add_memory, integration_start_time=integration_start_time)
|
|
273
243
|
return (h_with_memory, psi4_with_memory)
|