Skip to content

Commit 72e9b12

Browse files
Merge pull request #223 from vijayvarma392/rational_fit
add rational fits
2 parents 57f0b88 + 32ff1b7 commit 72e9b12

24 files changed

Lines changed: 924 additions & 372 deletions

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ All of these can be installed through pip or conda.
6565
* [lalsuite](https://pypi.org/project/lalsuite)
6666
* [sxs](https://github.com/sxs-collaboration/sxs)
6767
* [scri](https://github.com/moble/scri)
68+
* [polyrat](https://pypi.org/project/polyrat/)
6869

6970
# Usage
7071
See the example notebook [here](https://github.com/vijayvarma392/gw_eccentricity/blob/main/examples/gw_eccentricity_demo.ipynb) for a demo.

examples/gw_eccentricity_demo.ipynb

Lines changed: 131 additions & 60 deletions
Large diffs are not rendered by default.

examples/load_waveform_demo.ipynb

Lines changed: 43 additions & 13 deletions
Large diffs are not rendered by default.

gw_eccentricity/eccDefinition.py

Lines changed: 416 additions & 66 deletions
Large diffs are not rendered by default.

gw_eccentricity/gw_eccentricity.py

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -347,12 +347,47 @@ def measure_eccentricity(tref_in=None,
347347
Default: 0.
348348
349349
extra_kwargs: A dict of any extra kwargs to be passed. Allowed kwargs are:
350-
spline_kwargs:
350+
omega_gw_extrema_interpolation_method : str, default="rational_fit"
351+
Specifies the method used to build the interpolations for
352+
`omega_gw_pericenters_interp(t)` or
353+
`omega_gw_apocenters_interp(t)`. The available options are:
354+
355+
- `spline`: Uses `scipy.interpolate.InterpolatedUnivariateSpline`.
356+
- Best suited for cleaner data, such as when waveform modes
357+
are generated using models like SEOB or TEOB.
358+
- Faster to construct and evaluate.
359+
- Since it fits through every data point, it may exhibit
360+
oscillatory behavior, particularly near the merger,
361+
especially for noisy NR data.
362+
363+
- `rational_fit`: Uses `polyrat.StabilizedSKRationalApproximation`.
364+
- Can handle both clean and noisy data, e.g., waveform
365+
modes from numerical simulations.
366+
- Better monotonic behaviour, particularly near the merger.
367+
- Can be slower compared to the `spline` method.
368+
This is because finding optimal numerator and
369+
denominator degree may need several
370+
iterations. See under `get_rational_fit_for_extrema`
371+
- Can suppress pathologies in the waveform that might be
372+
seen with `spline`.
373+
374+
Default value: `"rational_fit"`.
375+
376+
special_interp_kwargs_for_omega_gw_extrema: dict
377+
A dictionary of kwargs for `omega_gw_extrema_interpolation_method`.
378+
The defaults are set according to the value of `omega_gw_extrema_interpolation_method`:
379+
380+
- if omega_gw_extrema_interpolation_method is "spline", default kwargs are set
381+
using `utils.get_default_spline_kwargs`.
382+
- if omega_gw_extrema_interpolation_method is "rational_fit", default kwargs are set
383+
using `utils.get_default_rational_fit_kwargs`.
384+
385+
general_interp_kwargs: dict
351386
Dictionary of arguments to be passed to the spline interpolation
352387
routine (scipy.interpolate.InterpolatedUnivariateSpline) used to
353-
compute quantities like omega_gw_pericenters(t) and
354-
omega_gw_apocenters(t).
355-
Defaults are set using utils.get_default_spline_kwargs
388+
interpolate data other than the omega_gw extrema.
389+
390+
Defaults are set using `utils.get_default_spline_kwargs`
356391
357392
extrema_finding_kwargs:
358393
Dictionary of arguments to be passed to the extrema finder,

gw_eccentricity/load_data.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1019,7 +1019,7 @@ def check_sxs_data_dir(origin, **kwargs):
10191019
f"file named `Strain_N{kwargs['extrap_order']}` since `extrap_order` "
10201020
f"is {kwargs['extrap_order']}."}
10211021
if any([kwargs["include_zero_ecc"], kwargs["include_params_dict"],
1022-
not kwargs["keep_memory"]]):
1022+
not kwargs.get("keep_memory", True)]):
10231023
# In newer versions of sxscatalog format, metadata.txt files are
10241024
# replaced by metadata.json file.
10251025
required_metadata_file = "metadata.json" if origin == "SXSCatalog" else "metadata.txt"
@@ -1085,7 +1085,7 @@ def make_return_dict_for_sxs_catalog_format(t, modes_dict, horizon_file_exits,
10851085
dataDict = {"t": t - tpeak,
10861086
"hlm": modes_dict}
10871087
if any([kwargs["include_zero_ecc"], kwargs["include_params_dict"],
1088-
not kwargs["keep_memory"]]):
1088+
not kwargs.get("keep_memory", True)]):
10891089
if os.path.exists(os.path.join(kwargs["data_dir"], "metadata.txt")):
10901090
params_dict = get_params_dict_from_sxs_metadata(
10911091
os.path.join(kwargs["data_dir"], "metadata.txt"))

gw_eccentricity/utils.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Useful functions for the project."""
22
import numpy as np
33
import argparse
4+
from polyrat import StabilizedSKRationalApproximation
45
from scipy.interpolate import InterpolatedUnivariateSpline
56
from scipy.interpolate import PchipInterpolator
67
import warnings
@@ -317,6 +318,44 @@ def get_interpolant(oldX,
317318
return interpolant
318319

319320

321+
def get_default_rational_fit_kwargs():
322+
"""Get default kwargs for rational fit."""
323+
default_rational_fit_kwargs = {
324+
"num_degree": None,
325+
"denom_degree": None,
326+
"norm": 2,
327+
"maxiter": 20,
328+
"verbose": False,
329+
"xtol": 1e-07,
330+
}
331+
return default_rational_fit_kwargs
332+
333+
334+
def get_rational_fit(x, y, rational_fit_kwargs=None, check_kwargs=True):
335+
"""Get rational fit for the data set (x, y).
336+
337+
We use `polyrat.StabilizedSKRationalApproximation` to obtain
338+
rational fit to the data.
339+
"""
340+
if check_kwargs:
341+
rational_fit_kwargs = check_kwargs_and_set_defaults(
342+
rational_fit_kwargs,
343+
get_default_rational_fit_kwargs(),
344+
"rational_fit_kwargs",
345+
"utils.get_default_rational_fit_kwargs"
346+
)
347+
# We use a rational approximation based on Stabilized Sanathanan-Koerner Iteration
348+
# described in arXiv:2009.10803 and implemented in `polyrat.StabilizedSKRationalApproximation`.
349+
rat = StabilizedSKRationalApproximation(**rational_fit_kwargs)
350+
# The input x-axis data must be 2-dimensional
351+
rat.fit(x.reshape(-1, 1), y)
352+
# Using a lambda function to change the input 1-d data to
353+
# float64 and reshape to 2-d as required by the fit.
354+
# Ensure also that the input and output types are the same.
355+
return lambda t: (rat(np.atleast_2d(t).astype('float64').reshape(-1, 1)).item() if np.isscalar(t)\
356+
else rat(np.atleast_2d(t).astype('float64').reshape(-1, 1)))
357+
358+
320359
def debug_message(message, debug_level, important=True,
321360
point_to_verbose_output=False):
322361
"""Show message based on debug_level.

pytest.ini

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[pytest]
2+
filterwarnings =
3+
ignore::DeprecationWarning:polyrat.*

setup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ def get_version():
3131
'h5py',
3232
'lalsuite',
3333
'sxs',
34-
'scri'
34+
'scri',
35+
'polyrat'
3536
],
3637
classifiers=[
3738
"Intended Audience :: Science/Research",

test/generate_regression_data.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,15 @@
1717
type=str,
1818
required=True,
1919
help="EccDefinition method to save the regression data for.")
20+
parser.add_argument(
21+
"--interp_method",
22+
type=str,
23+
required=True,
24+
help="omega_gw_extrema_interpolation_method to save the regression data for.")
2025
args = parser.parse_args()
2126

2227

23-
def generate_regression_data(method):
28+
def generate_regression_data(method, interp_method):
2429
"""Generate data for regression test using a method."""
2530
# Load test waveform
2631
lal_kwargs = {"approximant": "EccentricTD",
@@ -42,7 +47,7 @@ def generate_regression_data(method):
4247
raise Exception(f"method {method} is not available. Must be one of "
4348
f"{available_methods}")
4449

45-
extra_kwargs = {}
50+
extra_kwargs = {"omega_gw_extrema_interpolation_method": interp_method}
4651
user_kwargs = extra_kwargs.copy()
4752
regression_data.update({"extra_kwargs": extra_kwargs})
4853
# Try evaluating at an array of times
@@ -56,9 +61,9 @@ def generate_regression_data(method):
5661
meanano_ref = gwecc_dict["mean_anomaly"]
5762
# We save the measured data 3 reference times
5863
n = len(tref_out)
59-
dict_tref = {"time": [tref_out[0], tref_out[n//4], tref_out[n//2]],
60-
"eccentricity": [ecc_ref[0], ecc_ref[n//4], ecc_ref[n//2]],
61-
"mean_anomaly": [meanano_ref[0], meanano_ref[n//4], meanano_ref[n//2]]}
64+
dict_tref = {"time": [tref_out[n//8], tref_out[n//4], tref_out[n//2]],
65+
"eccentricity": [ecc_ref[n//8], ecc_ref[n//4], ecc_ref[n//2]],
66+
"mean_anomaly": [meanano_ref[n//8], meanano_ref[n//4], meanano_ref[n//2]]}
6267

6368
# Try evaluating at an array of frequencies
6469
gwecc_dict = measure_eccentricity(
@@ -70,18 +75,18 @@ def generate_regression_data(method):
7075
ecc_ref = gwecc_dict["eccentricity"]
7176
meanano_ref = gwecc_dict["mean_anomaly"]
7277
n = len(fref_out)
73-
dict_fref = {"frequency": [fref_out[0], fref_out[n//4], fref_out[n//2]],
74-
"eccentricity": [ecc_ref[0], ecc_ref[n//4], ecc_ref[n//2]],
75-
"mean_anomaly": [meanano_ref[0], meanano_ref[n//4], meanano_ref[n//2]]}
78+
dict_fref = {"frequency": [fref_out[n//8], fref_out[n//4], fref_out[n//2]],
79+
"eccentricity": [ecc_ref[n//8], ecc_ref[n//4], ecc_ref[n//2]],
80+
"mean_anomaly": [meanano_ref[n//8], meanano_ref[n//4], meanano_ref[n//2]]}
7681
regression_data.update({"tref": dict_tref,
7782
"fref": dict_fref})
7883

7984
if not os.path.exists(data_dir):
8085
os.mkdir(data_dir)
8186
# save to a json file
82-
fl = open(f"{data_dir}/{method}_regression_data.json", "w")
87+
fl = open(f"{data_dir}/{method}_{interp_method}_regression_data.json", "w")
8388
json.dump(regression_data, fl)
8489
fl.close()
8590

8691
# generate regression data
87-
generate_regression_data(args.method)
92+
generate_regression_data(args.method, args.interp_method)

0 commit comments

Comments
 (0)