Skip to content

Commit a237dcc

Browse files
Akash-Maurya-0899prayushCopilot
authored
Add support for SEOBNRv5HM and SEOBNRv5PHM as PMR piece (#10)
* Add support for SEOBNRv5HM and SEOBNRv5PHM for PMR piece - added support for using SEOBNRv5HM and SEOBNRv5PHM as the PMR piece in ESIGMA and ESIGMASur via pyseobnr - pyseobnr kept as an optional dependency; code uses lazy imports to avoid import errors if pyseobnr waveform models aren't used and pyseobnr is not installed - added exception handling for MR generation failure - Update README for additional merger-ringdown choices - Update ESIGMA usage notebook --------- Co-authored-by: Prayush Kumar <prayush@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent f2582bb commit a237dcc

8 files changed

Lines changed: 705 additions & 144 deletions

File tree

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Then to use:
2020
* `ESIGMAHM`: requires custom `LALSuite` installation and `NRSur7dq4` surrogate data download ([installation instructions below](https://github.com/gwnrtools/esigmapy/tree/master?tab=readme-ov-file#blue_square-esigmahm)).
2121
* `ESIGMASur`: requires downloading `ESIGMASur` surrogate data ([installation instructions below](https://github.com/gwnrtools/esigmapy/tree/master?tab=readme-ov-file#green_square-esigmasur))
2222

23-
Usage instructions at:
23+
Usage instructions:
2424
* `ESIGMAHM` [tutorial notebook](https://github.com/gwnrtools/esigmapy/blob/master/notebooks/ESIGMA_tutorial.ipynb)
2525
* `ESIGMASur` [tutorial notebook](https://github.com/gwnrtools/esigmapy/blob/master/notebooks/ESIGMASur_tutorial.ipynb)
2626
***
@@ -31,7 +31,7 @@ Usage instructions at:
3131
* **Inspiral piece (`InspiralESIGMAHM`):** It comes from a combination of post-Newtonian theory, self-force, and black hole perturbation theory.
3232
* **Plunge-merger-ringdown piece**: Assuming moderate starting eccentricities that decay by the late inspiral, we use the quasi-circular (QC) NR surrogate `NRSur7dq4` for the plunge-merger-ringdown piece for `ESIGMAHM`.
3333

34-
(**Note:** We also allow other QC `LALSuite` waveform models to be used as the plunge-merger-ringdown piece; see the optional argument `merger_ringdown_approximant` in the generation functions `get_imr_esigma_waveform` and `get_imr_esigma_modes` [here](https://github.com/gwnrtools/esigmapy/blob/master/esigmapy/generator.py). However, the default and the most tested choice is `NRSur7dq4`.)
34+
(**Note:** We also allow other QC waveform models to be used as the plunge-merger-ringdown piece; see the optional argument `merger_ringdown_approximant` in the generation functions `get_imr_esigma_waveform` and `get_imr_esigma_modes` [here](https://github.com/gwnrtools/esigmapy/blob/master/esigmapy/generator.py). The models currently supported are: `NRSur7dq4`, `SEOBNRv4PHM` (via `LALSuite`), `SEOBNRv5HM`, `SEOBNRv5PHM` (via `pyseobnr`). However, the default and the most tested choice is `NRSur7dq4`.)
3535

3636
The full IMR waveform `ESIGMAHM` is produced by smoothly attaching the inspiral piece `InspiralESIGMAHM` to the plunge-merger-ringdown piece `NRSur7dq4`. This attachment is facilitated by `ESIGMAPy`.
3737

@@ -73,8 +73,8 @@ Using `ESIGMAHM` therefore requires installing 1. `InspiralESIGMAHM`, 2. `NRSur7
7373
* To avoid performing the above step in every new terminal session, either add the above command to your `.bashrc` file, or follow the instructions [here](http://gitlab.icts.res.in/akash.maurya/Installation-instructions/wikis/conda-tricks), replacing `PYTHONPATH` with `LAL_DATA_PATH`, to set this environment variable automatically on activating your `conda` environment.
7474
7575
### Installing `ESIGMAPy`
76-
* Activate your `conda` environment and install `ESIGMAPy` by running: `pip install esigmapy`.
77-
76+
* Activate your `conda` environment and install `ESIGMAPy` by running: `pip install git+https://github.com/gwnrtools/esigmapy.git`.
77+
* (Optional) If you want to use `SEOBNRv5HM` or `SEOBNRv5PHM` as the merger-ringdown piece in `ESIGMAHM`, install `ESIGMAPy` with `pyseobnr` option by running: `pip install "esigmapy[pyseobnr] @ git+https://github.com/gwnrtools/esigmapy.git"`
7878
***
7979
### Trying out `ESIGMAHM`
8080
The instructions to generate `ESIGMAHM` waveforms and the various functionalities that it offers are detailed in [this tutorial notebook](https://github.com/gwnrtools/esigmapy/blob/master/notebooks/ESIGMA_tutorial.ipynb).
@@ -99,7 +99,7 @@ The instructions to generate `ESIGMAHM` waveforms and the various functionalitie
9999
100100
* Download the surrogate data files of `ESIGMASur`, which can be found [on the repo](https://github.com/gwnrtools/esigmapy/tree/master/esigmapy/surrogate/data). Next, set the shell environment variable `ESIGMASUR_DATA_PATH` to the directory where you keep these surrogate data files by running: `export ESIGMASUR_DATA_PATH="/path/to/ESIGMASur"`.
101101
102-
* Using the inspiral-only surrogate `InspiralESIGMASur` would not require any further dependencies. However, its hybridized IMR version `IMRESIGMASur` will require downloading the NR surrogate data file ([installation instructions above](https://github.com/gwnrtools/esigmapy/tree/master#installing-nrsur7dq4)).
102+
* Using the inspiral-only surrogate `InspiralESIGMASur` would not require any further dependencies. However, its hybridized IMR version `IMRESIGMASur` will require downloading the NR surrogate data file ([installation instructions above](https://github.com/gwnrtools/esigmapy/tree/master#installing-nrsur7dq4)). Or, if you want to use `SEOBNRv5HM` or `SEOBNRv5PHM` as the merger-ringdown piece in `IMRESIGMASur`, add `pyseobnr` option during the installation: `pip install "esigmapy[surrogate,pyseobnr] @ git+https://github.com/gwnrtools/esigmapy.git"`.
103103
104104
### Trying out `ESIGMASur`
105105
The usage instructions and the various functionalities of `ESIGMASur` are detailed in [this tutorial notebook](https://github.com/gwnrtools/esigmapy/blob/master/notebooks/ESIGMASur_tutorial.ipynb).

esigmapy/__init__.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from __future__ import absolute_import
22

3-
from . import blend, generator, legacy, utils
3+
from . import blend, generator, mr_generator, legacy, utils
44
from .generator import *
55

66

@@ -19,23 +19,32 @@ def get_version_information():
1919

2020
# pycbc wrapper
2121
import inspect
22+
2223
_params = inspect.signature(get_imr_esigma_waveform).parameters.keys()
24+
25+
2326
def pycbc_esigma(**params):
2427
from pycbc.waveform.waveform import parse_mode_array
2528

2629
# Pass all parameters the model suppports
2730
pt = {p: params[p] for p in _params if p in params}
28-
pt['f_ref'] = pt['f_lower'] if pt['f_ref'] == 0 else pt['f_ref']
31+
pt["f_ref"] = pt["f_lower"] if pt["f_ref"] == 0 else pt["f_ref"]
2932

3033
gen_wav = get_imr_esigma_waveform
31-
if 'skip_merger' in params:
34+
if "skip_merger" in params:
3235
gen_wav = get_inspiral_esigma_waveform
3336

34-
modes = params.pop('mode_array', [(2,2), (2,-2)])
35-
modes = parse_mode_array({'mode_array':modes})['mode_array']
37+
modes = params.pop("mode_array", [(2, 2), (2, -2)])
38+
modes = parse_mode_array({"mode_array": modes})["mode_array"]
39+
40+
return gen_wav(
41+
pt.pop("mass1"),
42+
pt.pop("mass2"),
43+
pt.pop("f_lower"),
44+
pt.pop("delta_t"),
45+
**pt,
46+
modes_to_use=modes
47+
)
3648

37-
return gen_wav(pt.pop('mass1'), pt.pop('mass2'),
38-
pt.pop('f_lower'), pt.pop('delta_t'),
39-
**pt, modes_to_use=modes)
4049

4150
__version__ = get_version_information()

esigmapy/generator.py

Lines changed: 46 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import lalsimulation as ls
1212
import pycbc.types as pt
1313
from .utils import f_ISCO_spin
14+
from .mr_generator import check_available_mr_approximants, get_mr_modes
1415

1516
ECCENTRICITY_LEVEL_ISCO_WARNING = 0.02
1617
ECCENTRICITY_LEVEL_ISCO_ERROR = 0.1
@@ -45,7 +46,7 @@ def find_x_for_y(x, y, y0):
4546
t.data.data *= lal.MTSUN_SI
4647

4748
if verbose:
48-
print(f"Orbital evolution took: {time.perf_counter() - itime} seconds")
49+
print(f"Inspiral orbital evolution took: {time.perf_counter() - itime} seconds")
4950

5051
omega = pt.TimeSeries(phidot.data.data, delta_t=t.data.data[1] - t.data.data[0])
5152
if extremum == "periastron":
@@ -131,7 +132,7 @@ def eccentricity_at_reference_frequency(
131132
t.data.data *= lal.MTSUN_SI
132133

133134
if verbose:
134-
print(f"Orbital evolution took: {time.perf_counter() - itime} seconds")
135+
print(f"Inspiral orbital evolution took: {time.perf_counter() - itime} seconds")
135136

136137
x_reference = (np.pi * (mass1 + mass2) * lal.MTSUN_SI * f_reference) ** (2.0 / 3.0)
137138

@@ -189,7 +190,8 @@ def get_inspiral_esigma_modes(
189190
parameters.
190191
We require f_ref <= f_lower. f_ref = f_lower by default.
191192
delta_t -- Waveform's time grid-spacing (in s)
192-
spin1z, spin2z -- z-components of component dimensionless spins (lies in [0,1))
193+
spin1z, spin2z -- z-components of component dimensionless spins
194+
(lies in (-1, 1))
193195
eccentricity -- Initial eccentricity
194196
mean_anomaly -- Mean anomaly of the periastron (in rad)
195197
distance -- Luminosity distance to the binary (in Mpc)
@@ -278,7 +280,7 @@ def get_inspiral_esigma_modes(
278280
) * lal.MTSUN_SI # Time from geometrized units to seconds
279281

280282
if verbose:
281-
print(f"Orbital evolution took: {time.perf_counter() - itime} seconds")
283+
print(f"Inspiral orbital evolution took: {time.perf_counter() - itime} seconds")
282284

283285
# Include conjugate modes in the mode list
284286
if include_conjugate_modes:
@@ -319,7 +321,7 @@ def get_inspiral_esigma_modes(
319321
modes = {k: np.asarray(modes[k].data.data) for k in modes}
320322

321323
if verbose:
322-
print(f"Modes generation took: {time.perf_counter() - itime} seconds")
324+
print(f"Inspiral modes generation took: {time.perf_counter() - itime} seconds")
323325

324326
if return_orbital_params:
325327
orbital_var_dict = {}
@@ -372,12 +374,13 @@ def get_inspiral_esigma_waveform(
372374
parameters.
373375
We require f_ref <= f_lower. f_ref = f_lower by default.
374376
delta_t -- Waveform's time grid-spacing (in s)
375-
spin1z, spin2z -- z-components of component dimensionless spins (lies in [0,1))
377+
spin1z, spin2z -- z-components of component dimensionless spins
378+
(lies in (-1, 1))
376379
eccentricity -- Initial eccentricity
377380
mean_anomaly -- Mean anomaly of the periastron (in rad)
378381
inclination -- Inclination (in rad), defined as the angle between the orbital
379382
angular momentum L and the line-of-sight
380-
coa_phase -- Coalesence phase of the binary (in rad)
383+
coa_phase -- Coalescence phase of the binary (in rad)
381384
distance -- Luminosity distance to the binary (in Mpc)
382385
modes_to_use -- GW modes to use. List of tuples (l, |m|)
383386
return_orbital_params -- If True, returns the orbital evolution of all the orbital elements (in
@@ -675,7 +678,7 @@ def get_imr_esigma_modes(
675678
):
676679
"""
677680
Returns IMR GW modes constructed using ESIGMA for inspiral and
678-
NRSur7dq4/SEOBNRv4PHM for merger-ringdown
681+
NRSur7dq4/SEOBNRv4PHM/SEOBNRv5HM/SEOBNRv5PHM for merger-ringdown
679682
680683
Parameters:
681684
-----------
@@ -687,10 +690,10 @@ def get_imr_esigma_modes(
687690
f_ref = f_lower by default.
688691
delta_t -- Waveform's time grid-spacing (in s)
689692
spin1z, spin2z -- z-components of component dimensionless
690-
spins (lies in [0,1))
693+
spins (lies in (-1,1))
691694
eccentricity -- Initial eccentricity
692695
mean_anomaly -- Mean anomaly of the periastron (radians)
693-
coa_phase -- Coalesence phase of the binary (in rad)
696+
coa_phase -- Coalescence phase of the binary (in rad)
694697
distance -- Luminosity distance to the binary (in Mpc)
695698
modes_to_use -- GW modes to use. List of tuples (l, |m|)
696699
mode_to_align_by -- GW mode to use to align inspiral and merger
@@ -731,8 +734,10 @@ def get_imr_esigma_modes(
731734
the center of the hybridization window.
732735
Otherwise, it's kept at the end of the
733736
window (default).
734-
merger_ringdown_approximant -- Choose merger-ringdown model. Tested
735-
choices: [NRSur7dq4, SEOBNRv4PHM]
737+
merger_ringdown_approximant -- Choose merger-ringdown model.
738+
Available choices:
739+
NRSur7dq4, SEOBNRv4PHM (requires `lalsimulation`)
740+
SEOBNRv5HM, SEOBNRv5PHM (requires `pyseobnr`)
736741
return_hybridization_info -- If True, returns hybridization related data
737742
return_orbital_params -- If True, returns the orbital evolution of
738743
all the orbital elements (in
@@ -757,11 +762,7 @@ def get_imr_esigma_modes(
757762
retval -- Hybridization related data. Returned only if the
758763
flag `return_hybridization_info` is set
759764
"""
760-
if not hasattr(ls, merger_ringdown_approximant):
761-
raise IOError(
762-
"""We cannot generate individual modes for {merger_ringdown_approximant}.
763-
Try one of: [NRSur7dq4, SEOBNRv4PHM]"""
764-
)
765+
check_available_mr_approximants(merger_ringdown_approximant)
765766
if (mean_anomaly is None) and (coa_phase is None):
766767
raise IOError(
767768
f"""Please specify one of the phase angles, either of
@@ -965,47 +966,42 @@ def get_imr_esigma_modes(
965966
if not keep_f_mr_transition_at_center:
966967
f_mr_transition -= f_window_mr_transition / 2.0
967968

968-
# Generate NR surrogate waveform that will be our merger-ringdown, starting
969+
# Generate plunge-merger-ringdown waveform, starting
969970
# from a frequency = 90% of
970971
max_retries = 20
971972
f_lower_mr = (f_mr_transition - f_window_mr_transition / 2) * (
972973
1.8 / mode_to_align_by_em
973974
)
975+
modes_to_use = list(modes_inspiral_numpy.keys())
974976
for _ in range(max_retries):
975977
try:
976978
if verbose:
977979
print(f"Generating MR waveform from {f_lower_mr}Hz...")
978-
hlm_mr = ls.SimInspiralChooseTDModes(
979-
coa_phase, # phiRef
980-
delta_t, # deltaT
981-
mass1 * lal.MSUN_SI,
982-
mass2 * lal.MSUN_SI,
983-
0, # spin1x
984-
0, # spin1y
985-
spin1z,
986-
0, # spin2x
987-
0, # spin2y
988-
spin2z,
989-
f_lower_mr, # f_min
990-
f_lower_mr, # f_ref
991-
distance * lal.PC_SI * 1.0e6,
992-
None, # LALpars
993-
4, # lmax
994-
getattr(ls, merger_ringdown_approximant),
980+
modes_mr_numpy = get_mr_modes(
981+
mass1=mass1,
982+
mass2=mass2,
983+
f_lower=f_lower_mr,
984+
f_ref=f_lower_mr,
985+
delta_t=delta_t,
986+
spin1z=spin1z,
987+
spin2z=spin2z,
988+
coa_phase=coa_phase,
989+
distance=distance,
990+
modes_to_use=modes_to_use,
991+
approximant=merger_ringdown_approximant,
992+
verbose=verbose,
995993
)
996994
break
997995
except:
998996
f_lower_mr *= 0.8
999997
continue
1000-
# Extracting only the modes we need
1001-
modes_to_use = list(modes_inspiral_numpy.keys())
1002-
modes_mr_numpy = {}
1003-
while hlm_mr is not None:
1004-
key = (hlm_mr.l, hlm_mr.m)
1005-
if key in modes_to_use:
1006-
modes_mr_numpy[key] = hlm_mr.mode.data.data
1007-
hlm_mr = hlm_mr.next
1008-
998+
# else clause in a for-else block executes only if the
999+
# for-loop is not terminated by a break statement
1000+
else:
1001+
raise RuntimeError(
1002+
f"""Failed to generate merger-ringdown waveform after {max_retries} retries.
1003+
Last f_lower tried: {f_lower_mr/0.8:.4f}Hz."""
1004+
)
10091005
try:
10101006
retval = esigmapy.blend.blend_modes(
10111007
modes_inspiral_numpy,
@@ -1103,13 +1099,13 @@ def get_imr_esigma_waveform(
11031099
`f_ref = f_lower` by default.
11041100
delta_t -- Waveform's time grid-spacing (in s)
11051101
spin1z, spin2z -- z-components of component dimensionless
1106-
spins (lies in [0,1))
1102+
spins (lies in (-1,1))
11071103
eccentricity -- Initial eccentricity
11081104
mean_anomaly -- Mean anomaly of the periastron (in rad)
11091105
inclination -- Inclination (in rad), defined as the angle
11101106
between the orbital angular momentum L and
11111107
the line-of-sight
1112-
coa_phase -- Coalesence phase of the binary (in rad)
1108+
coa_phase -- Coalescence phase of the binary (in rad)
11131109
distance -- Luminosity distance to the binary (in Mpc)
11141110
modes_to_use -- GW modes to use. List of tuples (l, |m|)
11151111
mode_to_align_by -- GW mode to use to align inspiral and merger
@@ -1139,8 +1135,10 @@ def get_imr_esigma_waveform(
11391135
the center of the hybridization window.
11401136
Otherwise, it's kept at the end of the
11411137
window (default).
1142-
merger_ringdown_approximant -- Choose merger-ringdown model. Tested
1143-
choices: [NRSur7dq4, SEOBNRv4PHM]
1138+
merger_ringdown_approximant -- Choose merger-ringdown model.
1139+
Available choices:
1140+
NRSur7dq4, SEOBNRv4PHM (requires `lalsimulation`)
1141+
SEOBNRv5HM, SEOBNRv5PHM (requires `pyseobnr`)
11441142
return_hybridization_info -- If True, returns hybridization related data
11451143
return_orbital_params -- If True, returns the orbital evolution of
11461144
all the orbital elements (in

0 commit comments

Comments
 (0)