Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Astronomy Using Unevenly Sampled Data : GSoC 2023 #737

Merged
merged 31 commits into from
Sep 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
e6066c5
LSCross,Power Spectrum Classes implemented
pupperemeritus Jun 7, 2023
bf459e7
moved lsft funcs to fourier, added lombscargle to __init__.py
pupperemeritus Jun 7, 2023
1354896
Input of lsft_slow,fast changed to be non stingray specific, added c…
pupperemeritus Jun 10, 2023
7a71393
Removed LC and eventlist imports in fourier.py
pupperemeritus Jun 10, 2023
315a116
Typos and added fast version of lsft to fourier.py
pupperemeritus Jun 21, 2023
012c2e2
Integrating fast method into the cross,power spectrum classes
pupperemeritus Jun 21, 2023
ed68d75
Fixed the fast LSFT implementation and refactoring variable names
pupperemeritus Jun 28, 2023
f225487
Added basic tests, corrected algorithms wrt papers, typos in docstrings
pupperemeritus Jul 29, 2023
0b91e8b
Documentation, Algorithm Fixes, API Change
pupperemeritus Aug 20, 2023
a3bd5d2
Edited docs(core.rst,lscs,lsps), modified function, docstrings, forma…
pupperemeritus Aug 23, 2023
4eb2be5
Fixed Tests
pupperemeritus Aug 23, 2023
e1e6bc5
Improved Coverage
pupperemeritus Aug 31, 2023
9efe009
Comment code
matteobachetti Sep 5, 2023
fd91bc4
Test time lag
matteobachetti Sep 5, 2023
bc6541a
Phase lag fixes, code cleanup and optimization
pupperemeritus Sep 6, 2023
ed96e46
Test Coverage Improvement
pupperemeritus Sep 7, 2023
f5d47b1
Updated docs and rewrote the classes to use modern interface from Ave…
pupperemeritus Sep 17, 2023
0c37abc
Example eventlist test
pupperemeritus Sep 18, 2023
68fcf81
Code Cleanup, Tests and lc warning changes
pupperemeritus Sep 20, 2023
d89f0f5
method test fix
pupperemeritus Sep 21, 2023
ccf0af0
Reintroduced ls notebooks
pupperemeritus Sep 22, 2023
53521ff
Removed largememory
pupperemeritus Sep 22, 2023
c55c0d6
fix for python 3.11 tests
pupperemeritus Sep 22, 2023
8d23144
Fix the nphots parameter
matteobachetti Sep 26, 2023
1957239
Import all from lombscargle
matteobachetti Sep 26, 2023
da9e4b6
Make the autofrequency calculation more consistent and controllable
matteobachetti Sep 27, 2023
436db70
Test maxfreq <0
matteobachetti Sep 27, 2023
03606c8
Update notebooks
matteobachetti Sep 28, 2023
42087dd
Add reference to Lomb-Scargle tutorial
matteobachetti Sep 28, 2023
8a162a5
Fix missing whitespace [docs only]
matteobachetti Sep 28, 2023
01a1533
Refresh front page [docs only]
matteobachetti Sep 28, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/changes/737.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- Implemented the Lomb Scargle Fourier Transform (fast and slow versions)
- Using which wrote the corresponding :class:`LombScargleCrossspectrum` and :class:`LombScarglePowerspectrum`
23 changes: 20 additions & 3 deletions docs/core.rst
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
Core Stingray Functionality
***************************

Here we show how many of the core Stingray classes and methods
work in practice. We start with basic data constructs for
event data and light curve data, and then show how to produce
Here we show how many of the core Stingray classes and methods
work in practice. We start with basic data constructs for
event data and light curve data, and then show how to produce
various Fourier products from these data sets.

Working with Event Data
Expand Down Expand Up @@ -85,3 +85,20 @@ Multi-taper Periodogram
:maxdepth: 2

notebooks/Multitaper/multitaper_example.ipynb


Lomb Scargle Crossspectrum
--------------------------
.. toctree::
:maxdepth: 2

notebooks/LombScargle/LombScargleCrossspectrum_tutorial.ipynb


Lomb Scargle Powerspectrum
--------------------------

.. toctree::
:maxdepth: 2

notebooks/LombScargle/LombScarglePowerspectrum_tutorial.ipynb
12 changes: 12 additions & 0 deletions docs/dataexplo.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,15 @@ black hole binary using NICER data.
:maxdepth: 2

notebooks/Spectral Timing/Spectral Timing Exploration.ipynb


Studying very slow variability with the Lomb-Scargle periodogram
================================================================

In this Tutorial, we will show an example of how to use the Lomb-Scargle
periodogram and cross spectrum to study very slow variability in a light curve.

.. toctree::
:maxdepth: 2

notebooks/LombScargle/Very slow variability with Lomb-Scargle methods.ipynb
27 changes: 18 additions & 9 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,26 +21,35 @@ Features
Current Capabilities
--------------------

Currently implemented functionality in this library comprises:
1. Data handling and simulation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

* loading event lists from fits files of a few missions (RXTE/PCA, NuSTAR/FPM, XMM-Newton/EPIC, NICER/XTI)
* constructing light curves from event data, various operations on light curves (e.g. addition, subtraction, joining, and truncation)
* simulating a light curve with a given power spectrum
* simulating a light curve from another light curve and a 1-d (time) or 2-d (time-energy) impulse response
* simulating an event list from a given light curve _and_ with a given energy spectrum
* Good Time Interval operations
* power spectra in Leahy, rms normalization, absolute rms and no normalization
* averaged power spectra
* dynamical power spectra

2. Fourier methods
~~~~~~~~~~~~~~~~~~
* power spectra and cross spectra in Leahy, rms normalization, absolute rms and no normalization
* averaged power spectra and cross spectra
* dynamical power spectra and cross spectra
* maximum likelihood fitting of periodograms/parametric models
* (averaged) cross spectra
* coherence, time lags
* cross correlation functions
* RMS spectra and lags (time vs energy, time vs frequency); *needs testing*
* Variability-Energy spectra, like covariance spectra and lags *needs testing*
* covariance spectra; *needs testing*
* bispectra; *needs testing*
* (Bayesian) quasi-periodic oscillation searches
* simulating a light curve with a given power spectrum
* simulating a light curve from another light curve and a 1-d (time) or 2-d (time-energy) impulse response
* simulating an event list from a given light curve _and_ with a given energy spectrum
* Lomb-Scargle periodograms and cross spectra

3. Other time series methods
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* pulsar searches with Epoch Folding, :math:`Z^2_n` test
* Gaussian Processes for QPO studies
* cross correlation functions

Future Plans
------------
Expand Down
2 changes: 2 additions & 0 deletions stingray/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from stingray.events import *
from stingray.lightcurve import *
from stingray.utils import *
from stingray.lombscargle import *
from stingray.powerspectrum import *
from stingray.crossspectrum import *
from stingray.multitaper import *
Expand All @@ -22,3 +23,4 @@
from stingray.stats import *
from stingray.bispectrum import *
from stingray.varenergyspectrum import *
from stingray.lombscargle import *
220 changes: 219 additions & 1 deletion stingray/fourier.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import copy
import warnings
from collections.abc import Iterable
from typing import Optional

import numpy as np
import numpy.typing as npt
from astropy.table import Table
from astropy.timeseries.periodograms.lombscargle.implementations.utils import trig_sum

from .gti import (
generate_indices_of_segment_boundaries_binned,
generate_indices_of_segment_boundaries_unbinned,
)
from .utils import histogram, show_progress, sum_if_not_none_or_initialize, fft, fftfreq
from .utils import fft, fftfreq, histogram, show_progress, sum_if_not_none_or_initialize


def positive_fft_bins(n_bin, include_zero=False):
Expand Down Expand Up @@ -1985,3 +1988,218 @@ def avg_cs_from_events(
if results is not None:
results.meta["gti"] = gti
return results


def lsft_fast(
y: npt.ArrayLike,
t: npt.ArrayLike,
freqs: npt.ArrayLike,
sign: Optional[int] = 1,
oversampling: Optional[int] = 5,
) -> npt.NDArray:
"""
Calculates the Lomb-Scargle Fourier transform of a light curve.
Only considers non-negative frequencies.
Subtracts mean from data as it is required for the working of the algorithm.

Parameters
----------
y : a :class:`numpy.array` of floats
Observations to be transformed.

t : :class:`numpy.array` of floats
Times of the observations

freqs : :class:`numpy.array`
An array of frequencies at which the transform is sampled.

sign : int, optional, default: 1
The sign of the fourier transform. 1 implies positive sign and -1 implies negative sign.

fullspec : bool, optional, default: False
Return LSFT values for full frequency array (True) or just positive frequencies (False).

oversampling : float, optional, default: 5
Interpolation Oversampling Factor

Returns
-------
ft_res : numpy.ndarray
An array of Fourier transformed data.
"""
y_ = copy.deepcopy(y) - np.mean(y)
freqs = freqs[freqs >= 0]
# Constants initialization
sum_xx = np.sum(y_)
num_xt = len(y_)
num_ww = len(freqs)

# Arrays initialization
ft_real = ft_imag = np.zeros((num_ww))
f0, df, N = freqs[0], freqs[1] - freqs[0], len(freqs)

# Sum (y_i * cos(wt - wtau))
Sh, Ch = trig_sum(t, y_, df, N, f0, oversampling=oversampling)

# Angular Frequency
w = freqs * 2 * np.pi

# Summation of cos(2wt) and sin(2wt)
csum, ssum = trig_sum(
t, np.ones_like(y_) / len(y_), df, N, f0, freq_factor=2, oversampling=oversampling
)

wtau = 0.5 * np.arctan2(ssum, csum)

S2, C2 = trig_sum(
t,
np.ones_like(y_),
df,
N,
f0,
freq_factor=2,
oversampling=oversampling,
)

const = np.sqrt(0.5) * np.sqrt(num_ww)

coswtau = np.cos(wtau)
sinwtau = np.sin(wtau)

sumr = Ch * coswtau + Sh * sinwtau
sumi = Sh * coswtau - Ch * sinwtau

cos2wtau = np.cos(2 * wtau)
sin2wtau = np.sin(2 * wtau)

scos2 = 0.5 * (N + C2 * cos2wtau + S2 * sin2wtau)
ssin2 = 0.5 * (N - C2 * cos2wtau - S2 * sin2wtau)

ft_real = const * sumr / np.sqrt(scos2)
ft_imag = const * np.sign(sign) * sumi / np.sqrt(ssin2)

ft_real[freqs == 0] = sum_xx / np.sqrt(num_xt)
ft_imag[freqs == 0] = 0

phase = wtau - w * t[0]

ft_res = np.complex128(ft_real + (1j * ft_imag)) * np.exp(-1j * phase)

return ft_res


def lsft_slow(
y: npt.ArrayLike,
t: npt.ArrayLike,
freqs: npt.ArrayLike,
sign: Optional[int] = 1,
) -> npt.NDArray:
"""
Calculates the Lomb-Scargle Fourier transform of a light curve.
Only considers non-negative frequencies.
Subtracts mean from data as it is required for the working of the algorithm.

Parameters
----------
y : a `:class:numpy.array` of floats
Observations to be transformed.

t : `:class:numpy.array` of floats
Times of the observations

freqs : numpy.ndarray
An array of frequencies at which the transform is sampled.

sign : int, optional, default: 1
The sign of the fourier transform. 1 implies positive sign and -1 implies negative sign.

fullspec : bool, optional, default: False
Return LSFT values for full frequency array (True) or just positive frequencies (False).

Returns
-------
ft_res : numpy.ndarray
An array of Fourier transformed data.
"""
y_ = y - np.mean(y)
freqs = np.asarray(freqs[np.asarray(freqs) >= 0])

ft_real = np.zeros_like(freqs)
ft_imag = np.zeros_like(freqs)
ft_res = np.zeros_like(freqs, dtype=np.complex128)

num_y = y_.shape[0]
num_freqs = freqs.shape[0]
sum_y = np.sum(y_)
const1 = np.sqrt(0.5 * num_y)
const2 = const1 * np.sign(sign)
ft_real = ft_imag = np.zeros(num_freqs)
ft_res = np.zeros(num_freqs, dtype=np.complex128)

for i in range(num_freqs):
wrun = freqs[i] * 2 * np.pi
if wrun == 0:
ft_real = sum_y / np.sqrt(num_y)
ft_imag = 0
phase_this = 0
else:
# Calculation of \omega \tau (II.5) --
csum = np.sum(np.cos(2.0 * wrun * t))
ssum = np.sum(np.sin(2.0 * wrun * t))

watan = np.arctan2(ssum, csum)
wtau = 0.5 * watan
# --
# In the following, instead of t'_n we are using \omega t'_n = \omega t - \omega\tau

# Terms of kind X_n * cos or sin (II.1) --
sumr = np.sum(y_ * np.cos(wrun * t - wtau))
sumi = np.sum(y_ * np.sin(wrun * t - wtau))
# --

# A and B before the square root and inversion in (II.3) --
scos2 = np.sum(np.power(np.cos(wrun * t - wtau), 2))
ssin2 = np.sum(np.power(np.sin(wrun * t - wtau), 2))

# const2 is const1 times the sign.
# It's the F0 in II.2 without the phase factor
# The sign decides whether we are calculating the direct or inverse transform
ft_real = const1 * sumr / np.sqrt(scos2)
ft_imag = const2 * sumi / np.sqrt(ssin2)

phase_this = wtau - wrun * t[0]

ft_res[i] = np.complex128(ft_real + (1j * ft_imag)) * np.exp(-1j * phase_this)
return ft_res


def impose_symmetry_lsft(
ft_res: npt.ArrayLike,
sum_y: float,
len_y: int,
freqs: npt.ArrayLike,
) -> npt.ArrayLike:
"""
Impose symmetry on the input fourier transform.

Parameters
----------
ft_res : np.array
The Fourier transform of the signal.
sum_y : float
The sum of the values of the signal.
len_y : int
The length of the signal.
freqs : np.array
An array of frequencies at which the transform is sampled.

Returns
-------
lsft_res : np.array
The Fourier transform of the signal with symmetry imposed.
freqs_new : np.array
The new frequencies
"""
ft_res_new = np.concatenate([np.conjugate(np.flip(ft_res)), [0.0], ft_res])
freqs_new = np.concatenate([np.flip(-freqs), [0.0], freqs])
return ft_res_new, freqs_new
1 change: 1 addition & 0 deletions stingray/lightcurve.py
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,7 @@ def check_lightcurve(self):
"Bin sizes in input time array aren't equal throughout! "
"This could cause problems with Fourier transforms. "
"Please make the input time evenly sampled."
"Only use with LombScargleCrossspectrum, LombScarglePowerspectrum and QPO using GPResult"
)

def _operation_with_other_lc(self, other, operation):
Expand Down
Loading