IMRPhenomXHM
IMRPhenomXHM.py — JAX port of LAL's PhenomXHM higher-mode infrastructure.
Ports the 122019-release code from
LALSimIMRPhenomXHM_qnm.c — QNM fits (fRING, fDAMP per mode) LALSimIMRPhenomXHM_internals.c — waveform struct, coefficient computation LALSimIMRPhenomXHM_inspiral.c — inspiral parameter-space fits LALSimIMRPhenomXHM_intermediate.c — intermediate parameter-space fits LALSimIMRPhenomXHM_ringdown.c — ringdown parameter-space fits LALSimIMRPhenomX_internals.c — IMRPhenomX_TimeShift_22, XLALSimIMRPhenomXLinb
The 22-mode reuses ripple's existing IMRPhenomXAS.py (Phase + Amp functions).
Functions:
| Name | Description |
|---|---|
IMRPhenomX_TimeShift_22 |
Global time shift for the 22 mode that aligns the waveform peak near t=0. |
XLALSimIMRPhenomXHMEvaluateOnehlmMode |
Evaluate complex hlm for one mode at all frequencies. |
XLALSimIMRPhenomXHMGethlmModes |
Generate all requested higher modes in geometric units. |
XLALSimIMRPhenomXLinb |
Linear-in-frequency coefficient of the 22 phase (group delay offset). |
XLALSimIMRPhenomXPsi4ToStrain |
Psi4-to-strain conversion factor for the time-shift. |
build_pWF22 |
Build the 22-mode waveform parameter dict needed by XHM functions. |
evaluate_QNMfit_fdamp21 |
Damping frequency for (2,1) mode as a function of final spin. |
evaluate_QNMfit_fdamp32 |
Damping frequency for (3,2) mode. |
evaluate_QNMfit_fdamp33 |
Damping frequency for (3,3) mode. |
evaluate_QNMfit_fdamp44 |
Damping frequency for (4,4) mode. |
evaluate_QNMfit_fring21 |
Ringdown frequency for (2,1) mode as a function of final spin. |
evaluate_QNMfit_fring32 |
Ringdown frequency for (3,2) mode. |
evaluate_QNMfit_fring33 |
Ringdown frequency for (3,3) mode. |
evaluate_QNMfit_fring44 |
Ringdown frequency for (4,4) mode. |
gen_IMRPhenomXHM_hphc |
Generate IMRPhenomXHM hp, hc polarizations for an aligned-spin binary. |
xhm_amp_noModeMixing |
Evaluate the (l,m) mode amplitude at frequencies Mf (no mode mixing). |
xhm_get_amp_coefficients |
Compute all amplitude coefficients for one higher mode. |
xhm_get_phase_coefficients |
Solve for all phase coefficients of one higher mode (non-32). |
xhm_phase_noModeMixing |
Evaluate the (l,m) mode phase at frequencies Mf (no mode mixing: 21, 33, 44). |
xhm_set_waveform_variables |
Compute per-mode struct from the 22-mode waveform parameters. |
XHMAmpCoefficients
dataclass
¤
All amplitude coefficients for one higher mode. Populated by xhm_get_amp_coefficients.
XHMPhaseCoefficients
dataclass
¤
All phase coefficients for one higher mode. Populated by xhm_get_phase_coefficients.
XHMWaveformStruct
dataclass
¤
Per-mode waveform variables. Mirrors IMRPhenomXHMWaveformStruct from LALSimIMRPhenomXHM_structs.h. Populated by xhm_set_waveform_variables.
IMRPhenomX_TimeShift_22(pWF22: dict[str, Any]) -> float
¤
Global time shift for the 22 mode that aligns the waveform peak near t=0.
tshift = linb - dphi22Ref - 2pi(500 + psi4tostrain)
where
linb = XLALSimIMRPhenomXLinb(eta, STotR, dchi, delta) dphi22Ref = d/d(Mf) [IMRPhenomXAS_Phase] at fRING22 - fDAMP22 = jax.grad(IMRPhenomXAS_Phase)(frefFit_Hz) / M_s psi4tostrain = XLALSimIMRPhenomXPsi4ToStrain(eta, STotR, dchi)
PNR_DEV_PARAMETER is zero for default settings, so the NU0 correction is omitted. Port of IMRPhenomX_TimeShift_22 in LALSimIMRPhenomX_internals.c:2624. This is the critical fix for the t_c shift in PE.
XLALSimIMRPhenomXHMEvaluateOnehlmMode(freqs_geom: Float[Array, ' n_freq'], pWFHM: XHMWaveformStruct, pPhase: XHMPhaseCoefficients, pAmp: XHMAmpCoefficients, pWF22: dict[str, Any], t0: FloatLike, phi0: float | FloatLike) -> Complex[Array, ' n_freq']
¤
Evaluate complex hlm for one mode at all frequencies.
Returns complex array of shape (len(freqs_geom),): hlm(Mf) = Amp(Mf) * exp(-i * (t0(Mf - Mf_ref) + phase_lm(Mf) - emmphi0))
The t0 is IMRPhenomX_TimeShift_22 (not the DPhiMRD t0 from old HM). Source: IMRPhenomXHMEvaluateOnehlmMode in LALSimIMRPhenomXHM.c.
XLALSimIMRPhenomXHMGethlmModes(freqs_geom: Float[Array, ' n_freq'], pWF22: dict[str, Any], phi0: float | FloatLike, ell_mm_pairs: list) -> dict[tuple[int, int], Complex[Array, ' n_freq']]
¤
Generate all requested higher modes in geometric units.
Entry point called from IMRPhenomXPHM.py for the XPHM higher modes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
freqs_geom
|
Float[Array, ' n_freq']
|
frequency array in geometric units (M_total * f) |
required |
pWF22
|
dict[str, Any]
|
22-mode waveform parameter dict (from build_pWF22) |
required |
phi0
|
float | FloatLike
|
reference phase (coalescence phase) |
required |
ell_mm_pairs
|
list
|
list of (ell, mm) pairs, e.g. [(2,2),(2,1),(3,3),(3,2),(4,4)] |
required |
Returns:
| Type | Description |
|---|---|
dict[tuple[int, int], Complex[Array, ' n_freq']]
|
dict mapping (ell, mm) -> complex array of shape (len(freqs_geom),) |
Algorithm (mirrors LALSimIMRPhenomXHM.c main loop): 1. Compute t0 = IMRPhenomX_TimeShift_22. 2. Compute phifRef (22-mode reference phase). 3. Generate 22 mode via XAS Phase + t0 + phifRef. 4. For each higher mode (ell, mm) != (2,2): a. xhm_set_waveform_variables -> pWFHM b. xhm_get_phase_coefficients -> pPhase c. xhm_get_amp_coefficients -> pAmp d. XLALSimIMRPhenomXHMEvaluateOnehlmMode -> hlm
XLALSimIMRPhenomXLinb(eta: float, STotR: float, dchi: float, delta: float) -> float
¤
Linear-in-frequency coefficient of the 22 phase (group delay offset).
Port of XLALSimIMRPhenomXLinb in LALSimIMRPhenomXUtilities.c. Uses STotR (not the chi_eff-derived S) as the spin parameter.
XLALSimIMRPhenomXPsi4ToStrain(eta: float, STotR: float, dchi: float) -> FloatLike
¤
Psi4-to-strain conversion factor for the time-shift.
Port of XLALSimIMRPhenomXPsi4ToStrain in LALSimIMRPhenomXUtilities.c. Uses STotR as the spin parameter.
build_pWF22(m1: float | Array, m2: float | Array, chi1z: float | Array, chi2z: float | Array, f_ref: float, chip: float | Array = 0.0, msa_SAv2: float | Array | None = None, msa_S1L_pav: float | Array | None = None, msa_S2L_pav: float | Array | None = None) -> dict[str, Any]
¤
Build the 22-mode waveform parameter dict needed by XHM functions.
Contains all spin/mass combinations and 22-mode QNM frequencies. All frequencies are in geometric units (dimensionless: M_total * f in Hz).
chip: in-plane spin parameter for afinal_prec = sign(a)sqrt((chipmm1^2)^2+a^2). When called from XP/XPHM pass chiTot_perp (not chip_p), because LAL defaults to PhenomXPFinalSpinMod=4 which uses |S1_perp+S2_perp| / mm1^2 as the in-plane spin, giving Sperp = chiTot_perp*mm1^2 = |S1_perp+S2_perp|. This sets fRING22/fDAMP22 from the precessing final spin (pWF->afinal = afinal_prec).
pWF22 keys
eta, delta, S, STotR, dchi, chi1L, chi2L afinal, finmass (= 1 - Erad) fMECO, fRING22, fDAMP22 theta (tuple: m1_Msun, m2_Msun, chi1z, chi2z) phase_coeffs (PhenomD fitting coefficients for XAS phase) Mf_ref (geometric reference frequency)
evaluate_QNMfit_fdamp21(a: float) -> float
¤
Damping frequency for (2,1) mode as a function of final spin.
evaluate_QNMfit_fdamp32(a: float) -> float
¤
Damping frequency for (3,2) mode.
evaluate_QNMfit_fdamp33(a: float) -> float
¤
Damping frequency for (3,3) mode.
evaluate_QNMfit_fdamp44(a: float) -> float
¤
Damping frequency for (4,4) mode.
evaluate_QNMfit_fring21(a: float) -> float
¤
Ringdown frequency for (2,1) mode as a function of final spin.
evaluate_QNMfit_fring32(a: float) -> float
¤
Ringdown frequency for (3,2) mode.
evaluate_QNMfit_fring33(a: float) -> float
¤
Ringdown frequency for (3,3) mode.
evaluate_QNMfit_fring44(a: float) -> float
¤
Ringdown frequency for (4,4) mode.
gen_IMRPhenomXHM_hphc(freqs: Float[Array, ' n_freq'], theta: Float[Array, 8], f_ref: float) -> tuple[Complex[Array, ' n_freq'], Complex[Array, ' n_freq']]
¤
Generate IMRPhenomXHM hp, hc polarizations for an aligned-spin binary.
Matches LAL's SimInspiralChooseFDWaveform("IMRPhenomXHM").
theta = [m1, m2, chi1z, chi2z, dist_mpc, tc, phi_ref, iota] m1, m2 : component masses in solar masses chi1z/2z : aligned spins [-1, 1] dist_mpc : luminosity distance in Mpc tc : coalescence time (not used; included for interface consistency) phi_ref : orbital phase at reference frequency [rad] iota : inclination angle [rad]
Assembly mirrors LAL's IMRPhenomXHMFDAddMode (sym=1, phi=pi/2): For each mode (l, m>0): Ym = Y_{l,-m}^{-2}(iota, pi/2) = F_{l,-m}(iota) * (-i)^m Ystar = conj(Y_{l,m}^{-2}(iota, pi/2)) = F_{lm}(iota) * (-i)^m minus1l = (-1)^l factorp = 0.5 * (Ym + minus1l * Ystar) factorc = 0.5j * (Ym - minus1l * Ystar) hp += factorp * hlm hc += factorc * hlm where hlm is from XLALSimIMRPhenomXHMGethlmModes (positive-phase convention, same as LAL's h_{l,-m} FD mode at positive frequencies).
xhm_amp_noModeMixing(Mf: Float[Array, ' n_freq'], pAmp: XHMAmpCoefficients, pWFHM: XHMWaveformStruct) -> Float[Array, ' n_freq']
¤
Evaluate the (l,m) mode amplitude at frequencies Mf (no mode mixing).
Full strain amplitude = ampNorm * V(Mf) where: Inspiral (Mf < fAmpMatchIN): A(Mf) = ampNorm * Mf^(-7/6) * [PNgf|pn(Mf)| + rho1(Mf/fc)^(7/3) + ...] Intermediate (fAmpMatchIN <= Mf < fAmpMatchIM): A(Mf) = ampNorm / Q(Mf) where Q is degree-5 polynomial (inter_d) Ringdown (Mf >= fAmpMatchIM): A(Mf) = ampNorm * Mf^(-7/6) * fDAMP|alambda|sigmaexp(...)/(dfr^2+dfd^2)Mf^(-1/12)
Uses jnp.where for JAX-compatible branching. Source: IMRPhenomXHM_Amplitude_noModeMixing in LALSimIMRPhenomXHM.c.
xhm_get_amp_coefficients(pWFHM: XHMWaveformStruct, pWF22: dict[str, Any]) -> XHMAmpCoefficients
¤
Compute all amplitude coefficients for one higher mode.
Algorithm mirrors IMRPhenomXHM_GetAmplitudeCoefficients, 122022 release path: 1. Boundary frequencies (same for all modes in 122022). 2. PN coefficients. 3. Inspiral colloc pts (0.5/0.75/1.0)fIN, always version 13 (f1+f3 only). 4. RD colloc pts → vetos → compute (alambda, lambda_, sigma). 5. RD falloff: value + slope at fRING+2fDAMP. 6. Intermediate: direct polynomial f^(-7/6)*poly via linear solve.
xhm_get_phase_coefficients(pWFHM: XHMWaveformStruct, pWF22: dict[str, Any], t0: float) -> XHMPhaseCoefficients
¤
Solve for all phase coefficients of one higher mode (non-32).
Algorithm (mirrors IMRPhenomXHM_GetPhaseCoefficients in LALSimIMRPhenomXHM_internals.c): 1. Compute 6 collocation frequencies for intermediate region. 2. Evaluate p1..p6 fits + DeltaT (= t0 for XHM) to get derivative values. 3. Compute alpha2, alphaL for ringdown. 4. Compute phi0RD, dphi0RD (RD ansatz at fMatchIM with alpha0=0). 5. Select 5 collocation points based on eta/STotR (typical: [0,1,2,3,5]). 6. Build 5x5 matrix and solve for [c0, cL, c1, c2, c4]. 7. Compute C1INSP/CINSP continuity at fMatchIN. 8. Compute C1RD/CRD continuity at fMatchIM. 9. Compute deltaphiLM normalization at falign.
t0: IMRPhenomX_TimeShift_22 value (DeltaT added to each collocation point derivative). Note: 32-mode mixing not implemented (deferred to second pass).
xhm_phase_noModeMixing(Mf: Float[Array, ' n_freq'] | FloatLike, pPhase: XHMPhaseCoefficients, pWFHM: XHMWaveformStruct, pWF22: dict[str, Any], t0: FloatLike) -> Float[Array, ' n_freq'] | FloatLike
¤
Evaluate the (l,m) mode phase at frequencies Mf (no mode mixing: 21, 33, 44).
Three-region piecewise (all regions evaluated everywhere, switched by jnp.where): Mf < fMatchIN -> inspiral: (emm/2)phi_22(2Mf/emm/M_s) + LambdaPNMf + C1INSPMf + CINSP fMatchIN <= Mf < fMatchIM -> intermediate: c0Mf+c1log(Mf)-c2/Mf-c4/(3Mf^3)+cLatan(...) Mf >= fMatchIM -> ringdown: C1RDMf + CRD - fRD^2alpha2/Mf + alphaL*atan(...)
deltaphiLM is added as a global phase offset at evaluation time. Uses jnp.where for JAX-compatible branching. Source: IMRPhenomXHM_Phase_noModeMixing in LALSimIMRPhenomXHM.c.
xhm_set_waveform_variables(ell: int, emm: int, pWF22: dict[str, Any]) -> XHMWaveformStruct
¤
Compute per-mode struct from the 22-mode waveform parameters.
Mirrors IMRPhenomXHM_SetHMWaveformVariables in LALSimIMRPhenomXHM_internals.c.
pWF22 must be built by build_pWF22.