Skip to content

Commit a8600c9

Browse files
authored
Allow user to specify frequency grid parameters and add two bookkeeping outputs (#268)
1 parent 4638ca0 commit a8600c9

File tree

2 files changed

+82
-6
lines changed

2 files changed

+82
-6
lines changed

cesium/features/lomb_scargle.py

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ def lomb_scargle_model(
1313
tone_control=5.0,
1414
normalize=False,
1515
default_order=1,
16+
freq_grid=None,
1617
):
1718
"""Simultaneous fit of a sum of sinusoids by weighted least squares.
1819
@@ -51,6 +52,14 @@ def lomb_scargle_model(
5152
Order of polynomial to fit to the data while
5253
fitting the dominant frequency. Defaults to 1.
5354
55+
freq_grid : dict or None, optional
56+
Grid parameters to use. If None, then calculate the frequency
57+
grid automatically. To supply the grid, keys
58+
["f0", "fmax"] are expected. "f0" is the smallest frequency
59+
in the grid and "df" is the difference between grid points. If
60+
""df" is given (grid spacing) then the number of frequencies
61+
is calculated. if "numf" is given then "df" is inferred.
62+
5463
Returns
5564
-------
5665
dict
@@ -73,12 +82,27 @@ def lomb_scargle_model(
7382
dy0 = np.sqrt(error**2 + sys_err**2)
7483
wt = 1.0 / dy0**2
7584
chi0 = np.dot(signal**2, wt)
76-
77-
# TODO parametrize?
78-
f0 = 1.0 / max(time)
79-
df = 0.8 / max(time) # 20120202 : 0.1/Xmax
80-
fmax = 33.0 # pre 20120126: 10. # 25
81-
numf = int((fmax - f0) / df) # TODO !!! this is off by 1 point, fix?
85+
if freq_grid is None:
86+
f0 = 1.0 / max(time)
87+
df = 0.8 / max(time) # 20120202 : 0.1/Xmax
88+
fmax = 33.0 # pre 20120126: 10. # 25
89+
numf = int((fmax - f0) / df) + 1
90+
else:
91+
f0 = freq_grid["f0"]
92+
fmax = freq_grid["fmax"]
93+
df = freq_grid.get("df")
94+
tmp_numf = freq_grid.get("numf")
95+
if df is not None:
96+
# calculate numf
97+
numf = int((fmax - f0) / df) + 1
98+
elif tmp_numf is not None:
99+
df = (fmax - f0) / (tmp_numf - 1)
100+
numf = tmp_numf
101+
else:
102+
raise Exception("Both df and numf cannot be None.")
103+
104+
if f0 >= fmax:
105+
raise Exception(f"f0 {f0} should be smaller than fmax {fmax}")
82106

83107
model_dict = {"freq_fits": []}
84108
lambda0_range = [
@@ -127,6 +151,7 @@ def lomb_scargle_model(
127151
model_dict["nharm"] = nharm
128152
model_dict["chi2"] = current_fit["chi2"]
129153
model_dict["f0"] = f0
154+
model_dict["fmax"] = fmax
130155
model_dict["df"] = df
131156
model_dict["numf"] = numf
132157

@@ -429,6 +454,10 @@ def fit_lomb_scargle(
429454
ncp = norm.cumprod()
430455
out_dict["trend_coef"] = coef / ncp
431456
out_dict["y_offset"] = out_dict["trend_coef"][0] - cn0
457+
out_dict["trend_coef_error"] = np.sqrt(
458+
(1.0 / s0 + np.diag(np.dot(hat0.T, np.dot(hat_hat, hat0)))) / ncp**2
459+
)
460+
out_dict["y_offset_error"] = out_dict["trend_coef_error"][0]
432461

433462
prob = stats.f.sf(
434463
0.5

cesium/features/tests/test_lomb_scargle_features.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,53 @@
1616
WAVE_FREQS = np.array([5.3, 3.3, 2.1])
1717

1818

19+
def test_lomb_scargle_freq_grid():
20+
"""Test Lomb-Scargle model frequency grid calculation"""
21+
f0 = 0.5
22+
fmax = 10
23+
numf = 100
24+
df = 0.8
25+
frequencies = np.hstack((WAVE_FREQS[0], np.zeros(len(WAVE_FREQS) - 1)))
26+
amplitudes = np.zeros((len(frequencies), 4))
27+
amplitudes[0, :] = [8, 4, 2, 1]
28+
phase = 0.1
29+
times, values, errors = regular_periodic(frequencies, amplitudes, phase)
30+
model = lomb_scargle.lomb_scargle_model(
31+
times,
32+
values,
33+
errors,
34+
nharm=8,
35+
nfreq=1,
36+
freq_grid={"f0": f0, "fmax": fmax, "numf": numf},
37+
)
38+
39+
assert model["numf"] == numf
40+
# check that the spacing is correct
41+
npt.assert_allclose(
42+
model["freq_fits"][0]["freqs_vector"][1]
43+
- model["freq_fits"][0]["freqs_vector"][0],
44+
model["df"],
45+
)
46+
freqs = np.linspace(f0, fmax, numf)
47+
npt.assert_allclose(freqs[1] - freqs[0], model["df"])
48+
49+
model = lomb_scargle.lomb_scargle_model(
50+
times,
51+
values,
52+
errors,
53+
nharm=8,
54+
nfreq=1,
55+
freq_grid={"f0": f0, "fmax": fmax, "df": df},
56+
)
57+
assert model["numf"] == int((fmax - f0) / df) + 1
58+
59+
# check the autogeneration of the frequency grid
60+
model = lomb_scargle.lomb_scargle_model(
61+
times, values, errors, nharm=8, nfreq=1, freq_grid=None
62+
)
63+
npt.assert_allclose(model["f0"], 1.0 / (max(times) - min(times)))
64+
65+
1966
def test_lomb_scargle_regular_single_freq():
2067
"""Test Lomb-Scargle model features on regularly-sampled periodic data with
2168
one frequency/multiple harmonics. Estimated parameters should be very

0 commit comments

Comments
 (0)