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.
@@ -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(wa, wb, t1, t2, n_brute_force_δt=None, n_brute_force_δϕ=5, max_δt=np.inf, use_δΨ=False, include_modes=None, nprocs=None):
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 5, but if the option
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 = wa.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=1_000,
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 spherical
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)