Quick Start Guide
The key features of the StableEMRIFisher package is twofold
Robust Numerical Derivatives: Cheap and Robust EMRI waveform derivatives
Fisher Matrix Computations Fisher matrix calculations for accelerated parameter inference.
This quick-start guide will walk you through computing numerical derivatives and ultimately Fisher matrices.
Numerical Derivatives of Waveforms
Here’s an example of compute a parameter derivative of a detector frame Kerr Eccentric Equatorial waveform model
# Import relevant EMRI packages
from few.waveform import(
GenerateEMRIWaveform,
FastKerrEccentricEquatorialFlux
)
from few.utils.constants import YRSID_SI
# Import StableEMRIDerivative
from stableemrifisher.fisher.stablederivative import StableEMRIDerivative
import matplotlib.pyplot as plt
import numpy as np
# Define Waveform params
wave_params = {
"m1": 1e6, # Primary mass (solar masses)
"m2": 1e1, # Secondary mass (solar masses)
"a": 0.9, # Spin parameter
"p0": 10, # Initial semi-latus rectum
"e0": 0.4, # Initial eccentricity
"xI0": 1.0, # Initial inclination (equatorial orbits)
"dist": 1.0, # Distance to source
"qS": 0.2, # Sky location polar angle
"phiS": 0.8, # Sky location azimuthal angle
"qK": 1.6, # Orientation spin vector polar angle
"phiK": 1.5, # Orientation spin vector azimuthal angle
"Phi_phi0": 2.0, # Initial phase in phi0
"Phi_theta0": 0.0, # Initial phase in theta0
"Phi_r0": 3.0, # Initial phase in r0
}
# waveform class setup
waveform_class_kwargs = {
"inspiral_kwargs": {
"err": 1e-11,
},
"mode_selector_kwargs": {
"mode_selection_threshold": 1e-5
},
}
waveform_generator_kwargs = {"return_list": False, "frame": "detector"}
EMRI_deriv = StableEMRIDerivative(
FastKerrEccentricEquatorialFlux,
waveform_generator=GenerateEMRIWaveform,
waveform_generator_kwargs=waveform_generator_kwargs,
)
T = 0.01
dt = 10.0
kwargs = {"T": T, "dt": dt}
compute_stable_deriv = EMRI_deriv(
parameters=wave_params,
param_to_vary="m1",
delta=1e-1,
order=4,
kind="central",
**kwargs,
)
t = np.arange(0, T * YRSID_SI, dt)
plt.plot(t, compute_stable_deriv.real, c="blue", label="h_p: m1 partial derivative")
plt.xlabel("Time (s)", fontsize=16)
plt.ylabel("Derivative", fontsize=16)
plt.title("EMRI Derivative with respect to m1", fontsize=16)
plt.grid(True)
plt.show()
The code block above computes the numerical derivative of the waveform with respect to the primary mass m1 using a 4th order central finite difference stencil with a step size of delta=1e-1. The resulting derivative is plotted as a function of time.
Fisher Matrix
Detector Frame Waveforms
Extract parameter uncertainties from the Fisher matrix:
# Import relevant EMRI packages
from few.waveform import (
GenerateEMRIWaveform,
FastKerrEccentricEquatorialFlux,
)
# Import StableEMRIFisher
from stableemrifisher.fisher import StableEMRIFisher
import numpy as np
# Waveform params
dt = 5.0
T = 0.01
wave_params = {
"m1": 1e6,
"m2": 1e1,
"a": 0.9,
"p0": 10,
"e0": 0.4,
"xI0": 1.0,
"dist": 1.0,
"qS": 0.2,
"phiS": 0.8,
"qK": 1.6,
"phiK": 1.5,
"Phi_phi0": 2.0,
"Phi_theta0": 0.0,
"Phi_r0": 3.0,
}
# waveform class setup
waveform_class_kwargs = {
"inspiral_kwargs": {
"err": 1e-11,
},
"mode_selector_kwargs": {
"mode_selection_threshold": 1e-5
},
}
# waveform generator setup
waveform_generator = GenerateEMRIWaveform
waveform_generator_kwargs = {"return_list": False,
"frame": "detector"}
der_order = 4 # Order 4 stencil
Ndelta = 8 # Number of finite difference steps
# Initialise Fisher matrix class
# use latest KerrEccentricEquatorial waveform model
# with GenerateEMRIWaveform interface
sef = StableEMRIFisher(
waveform_class=FastKerrEccentricEquatorialFlux,
waveform_class_kwargs=waveform_class_kwargs,
waveform_generator=GenerateEMRIWaveform,
waveform_generator_kwargs=waveform_generator_kwargs,
dt=dt,
T=T,
der_order=der_order,
Ndelta=Ndelta,
deriv_type="stable",
)
# Specify what parameters to compute Fisher matrix for
param_names = [
"m1",
"m2",
"a",
]
# User can specify their own delta values to compute FM
# More advanced techniques to determine best value of
# finite difference deltas will be discussed later
deltas = np.array([1e-1, 1e-6, 1e-7])
# Compute Fisher matrix
fisher_matrix = sef(
wave_params,
param_names=param_names,
deltas=deltas,
)
# Compute parameter covariance matrix -- inverse of FM
param_cov = np.linalg.inv(fisher_matrix)
for k, item in enumerate(param_names):
print(
"Precision measurement in param {} is {}".format(
item, param_cov[k, k] ** (1 / 2)
)
)
One can use more advanced features with StableEMRIFisher to ensure convergence of the numerical derivatives.
sef = StableEMRIFisher(
waveform_class=FastKerrEccentricEquatorialFlux,
waveform_class_kwargs=waveform_class_kwargs,
waveform_generator=GenerateEMRIWaveform,
waveform_generator_kwargs=waveform_generator_kwargs,
dt=dt,
T=T,
stability_plot = True,
stats_for_nerds = True,
return_derivatives = True,
der_order=der_order,
Ndelta=Ndelta,
deriv_type="stable",
)
# Specify what parameters to compute Fisher matrix for
param_names = [
"m1",
"m2",
"a",
]
# StableEMRIFisher computes numerical derivatives and
# Fisher based scalars to identify the optimal finite difference
# step size within the intervals set below
delta_range = {
"m1": np.geomspace(1e2, 1e-3, Ndelta),
"m2": np.geomspace(1e-3, 1e-8, Ndelta),
"a": np.geomspace(1e-4, 1e-9, Ndelta),
}
# Compute Fisher matrix
param_derivs, fisher_matrix = sef(
wave_params,
param_names=param_names,
delta_range=delta_range
)
# Compute parameter covariance matrix -- inverse of FM
param_cov = np.linalg.inv(fisher_matrix)
for k, item in enumerate(param_names):
print(
"Precision measurement in param {} is {}".format(
item, param_cov[k, k] ** (1 / 2)
)
)
In the code block above, setting stability_plot = True shows a plot of the Fisher scalars \(\Gamma_{ii}\) as a function of finite difference step size. The optimal step size is chosen to be in the plateau region where the Fisher scalars are stable. The argument stats_for_nerds = True enables additional output which can be useful for debugging and understanding the behavior of the numerical derivatives.
With the Response Function
Use the time-domain Response function alongside state-of-the-art Power Spectral Densities (PSDs) for either first/second generation TDI variables to compute the Fisher Matrix.
# Import relevant EMRI packages
from few.waveform import (
GenerateEMRIWaveform,
FastKerrEccentricEquatorialFlux,
)
from stableemrifisher.fisher import StableEMRIFisher
from fastlisaresponse import ResponseWrapper # Response
from lisatools.detector import EqualArmlengthOrbits
import numpy as np
# Waveform params
dt = 5.0
T = 0.01
wave_params = {
"m1": 1e6,
"m2": 1e1,
"a": 0.9,
"p0": 10,
"e0": 0.4,
"xI0": 1.0,
"dist": 1.0,
"qS": 0.2,
"phiS": 0.8,
"qK": 1.6,
"phiK": 1.5,
"Phi_phi0": 2.0,
"Phi_theta0": 0.0,
"Phi_r0": 3.0,
}
####=======================True Responsed waveform==========================
waveform_class = FastKerrEccentricEquatorialFlux
waveform_class_kwargs = {
"inspiral_kwargs": {
"err": 1e-11,
},
"mode_selector_kwargs": {"mode_selection_threshold": 1e-5},
}
# waveform generator setup
waveform_generator = GenerateEMRIWaveform
waveform_generator_kwargs = {"return_list": False, "frame": "detector"}
# Response function set up
USE_GPU = False
tdi_kwargs = dict(
orbits=EqualArmlengthOrbits(use_gpu=USE_GPU),
order=25,
tdi="2nd generation",
tdi_chan="AE",
)
INDEX_LAMBDA = 8
INDEX_BETA = 7
# with longer signals we care less about this
t0 = 20000.0 # throw away on both ends when our orbital information is weird
ResponseWrapper_kwargs = dict(
Tobs = T,
dt = dt,
index_lambda = INDEX_LAMBDA,
index_beta = INDEX_BETA,
t0 = t0,
flip_hx = True,
use_gpu=USE_GPU,
is_ecliptic_latitude=False,
remove_garbage="zero",
**tdi_kwargs
)
der_order = 4
Ndelta = 8
stability_plot = False
sef = StableEMRIFisher(waveform_class=waveform_class,
waveform_class_kwargs=waveform_class_kwargs,
waveform_generator=waveform_generator,
waveform_generator_kwargs=waveform_generator_kwargs,
ResponseWrapper=ResponseWrapper, ResponseWrapper_kwargs=ResponseWrapper_kwargs,
stats_for_nerds = True, use_gpu = USE_GPU,
T = T, dt = dt,
der_order = der_order,
Ndelta = Ndelta,
stability_plot = stability_plot,
return_derivatives = False,
deriv_type='stable')
param_names = ['m1','m2','a']
delta_range = dict(
m1 = np.geomspace(1e3, 1e-5, Ndelta),
m2 = np.geomspace(1e-2, 1e-8, Ndelta),
a = np.geomspace(1e-5, 1e-9, Ndelta),
)
fisher_matrix = sef(wave_params, param_names = param_names,
delta_range = delta_range,
filename=None,
live_dangerously = False)
param_cov = np.linalg.inv(fisher_matrix)
for k, item in enumerate(param_names):
print("Precision measurement in param {} is {}".format(item, param_cov[k,k]**(1/2)))