Skip to content

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.