Skip to content

Commit

Permalink
Merge pull request #797 from StingraySoftware/fix_single_powerspectrum
Browse files Browse the repository at this point in the history
Fix problem with single power spectrum
  • Loading branch information
matteobachetti authored Feb 20, 2024
2 parents ac076c5 + 19d0c15 commit 5777271
Show file tree
Hide file tree
Showing 11 changed files with 229 additions and 64 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Bug Fixes
- Various bug fixes in DynamicalPowerspectrum, on event loading and time rebinning (`#779 <https://github.com/StingraySoftware/stingray/pull/779>`__)
- Fix issue with the Poisson noise calculation in lag spectra, that produced NaN errors under some conditions (`#789 <https://github.com/StingraySoftware/stingray/pull/789>`__)
- Fix rms computation and error bars (`#792 <https://github.com/StingraySoftware/stingray/pull/792>`__)

- Fix issue with ``Powerspectrum`` of a single light curve (`#663 <https://github.com/StingraySoftware/stingray/pull/663>`__)

Breaking Changes
^^^^^^^^^^^^^^^^
Expand Down
2 changes: 1 addition & 1 deletion docs/notebooks
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ filterwarnings =
ignore:.*is_categorical_dtype is deprecated.*:DeprecationWarning
ignore:.*datetime.datetime.utcfromtimestamp.*:DeprecationWarning
ignore:.*__array_wrap__ must accept context and return_scalar arguments:DeprecationWarning
ignore:.*Pyarrow:

;addopts = --disable-warnings

Expand Down
27 changes: 24 additions & 3 deletions stingray/crossspectrum.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from .gti import cross_two_gtis, time_intervals_from_gtis
from .lightcurve import Lightcurve
from .fourier import avg_cs_from_iterables, error_on_averaged_cross_spectrum
from .fourier import avg_cs_from_events, poisson_level
from .fourier import avg_cs_from_timeseries, poisson_level
from .fourier import normalize_periodograms, raw_coherence
from .fourier import get_flux_iterable_from_segments, power_color
from .fourier import get_rms_from_unnorm_periodogram
Expand Down Expand Up @@ -1566,6 +1566,27 @@ def _initialize_from_any_input(
``crossspectrum_from_XXXX`` function, and initialize ``self`` with
the correct attributes.
"""
if segment_size is None and isinstance(data1, StingrayObject):
common_gti = cross_two_gtis(data1.gti, data2.gti)
data1.gti = common_gti
data2.gti = common_gti
data1 = data1.apply_gtis(inplace=False)
data2 = data2.apply_gtis(inplace=False)

data1_is_binned = (
"counts" in data1.array_attrs() or "_counts" in data1.internal_array_attrs()
)
data2_is_binned = (
"counts" in data2.array_attrs() or "_counts" in data2.internal_array_attrs()
)

if data1_is_binned and data2_is_binned:
assert data2.time.size == data1.time.size and np.allclose(
data2.time - data1.time, 0
), "Time arrays are not the same"
elif data1_is_binned or data2_is_binned:
raise ValueError("Please use input data of the same kind")

if isinstance(data1, EventList):
spec = crossspectrum_from_events(
data1,
Expand Down Expand Up @@ -2444,7 +2465,7 @@ def crossspectrum_from_time_array(
force_averaged = segment_size is not None
# Suppress progress bar for single periodogram
silent = silent or (segment_size is None)
results = avg_cs_from_events(
results = avg_cs_from_timeseries(
times1,
times2,
gti,
Expand Down Expand Up @@ -2684,7 +2705,7 @@ def crossspectrum_from_timeseries(
err1 = getattr(ts1, error_flux_attr)
err2 = getattr(ts2, error_flux_attr)

results = avg_cs_from_events(
results = avg_cs_from_timeseries(
ts1.time,
ts2.time,
gti,
Expand Down
78 changes: 55 additions & 23 deletions stingray/fourier.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,14 @@
generate_indices_of_segment_boundaries_binned,
generate_indices_of_segment_boundaries_unbinned,
)
from .utils import fft, fftfreq, histogram, show_progress, sum_if_not_none_or_initialize
from .utils import (
fft,
fftfreq,
histogram,
show_progress,
sum_if_not_none_or_initialize,
fix_segment_size_to_integer_samples,
)


def integrate_power_in_frequency_range(
Expand Down Expand Up @@ -1497,7 +1504,7 @@ def get_flux_iterable_from_segments(
gti : [[gti00, gti01], [gti10, gti11], ...]
good time intervals
segment_size : float
length of segments
length of segments. If ``None``, the full light curve is used.
Other parameters
----------------
Expand Down Expand Up @@ -1527,12 +1534,20 @@ def get_flux_iterable_from_segments(
cast_kind = float
if dt is None and binned:
dt = np.median(np.diff(times[:100]))

if binned:
fluxes = np.asarray(fluxes)
if np.iscomplexobj(fluxes):
cast_kind = complex

fun = _which_segment_idx_fun(binned, dt)
if segment_size is None:
segment_size = gti[-1, 1] - gti[0, 0]

def fun(times, gti, segment_size):
return [[gti[0, 0], gti[-1, 1], 0, times.size]]

else:
fun = _which_segment_idx_fun(binned, dt)

for s, e, idx0, idx1 in fun(times, gti, segment_size):
if idx1 - idx0 < 2:
Expand Down Expand Up @@ -2228,7 +2243,15 @@ def local_show_progress(a):
return results


def avg_pds_from_events(
def avg_pds_from_events(*args, **kwargs):
warnings.warn(
"avg_pds_from_events is deprecated, use avg_cs_from_timeseries instead", DeprecationWarning
)

return avg_pds_from_timeseries(*args, **kwargs)


def avg_pds_from_timeseries(
times,
gti,
segment_size,
Expand Down Expand Up @@ -2256,7 +2279,7 @@ def avg_pds_from_events(
gti : [[gti00, gti01], [gti10, gti11], ...]
Good time intervals.
segment_size : float
Length of segments.
Length of segments. If ``None``, the full light curve is used.
dt : float
Time resolution of the light curves used to produce periodograms.
Expand Down Expand Up @@ -2294,13 +2317,14 @@ def avg_pds_from_events(
mean : float
the mean flux
"""
if segment_size is None:
segment_size = gti.max() - gti.min()
n_bin = int(segment_size / dt)
if fluxes is None:
dt = segment_size / n_bin
binned = fluxes is not None
if segment_size is not None:
segment_size, n_bin = fix_segment_size_to_integer_samples(segment_size, dt)
elif binned and segment_size is None:
n_bin = fluxes.size
else:
segment_size = n_bin * dt
_, n_bin = fix_segment_size_to_integer_samples(gti.max() - gti.min(), dt)

flux_iterable = get_flux_iterable_from_segments(
times, gti, segment_size, n_bin, dt=dt, fluxes=fluxes, errors=errors
)
Expand All @@ -2317,7 +2341,14 @@ def avg_pds_from_events(
return cross


def avg_cs_from_events(
def avg_cs_from_events(*args, **kwargs):
warnings.warn(
"avg_cs_from_events is deprecated, use avg_cs_from_timeseries instead", DeprecationWarning
)
return avg_cs_from_timeseries(*args, **kwargs)


def avg_cs_from_timeseries(
times1,
times2,
gti,
Expand Down Expand Up @@ -2353,7 +2384,7 @@ def avg_cs_from_events(
gti : [[gti00, gti01], [gti10, gti11], ...]
common good time intervals
segment_size : float
length of segments
length of segments. If ``None``, the full light curve is used.
dt : float
Time resolution of the light curves used to produce periodograms
Expand Down Expand Up @@ -2398,20 +2429,21 @@ def avg_cs_from_events(
n_ave : int
the number of averaged periodograms
"""
if segment_size is None:
segment_size = gti.max() - gti.min()
n_bin = int(segment_size / dt)
# adjust dt
# dt = segment_size / n_bin
if fluxes1 is None and fluxes2 is None:
dt = segment_size / n_bin

binned = fluxes1 is not None and fluxes2 is not None

if segment_size is not None:
segment_size, n_bin = fix_segment_size_to_integer_samples(segment_size, dt)
elif binned and segment_size is None:
n_bin = fluxes1.size
else:
segment_size = n_bin * dt
_, n_bin = fix_segment_size_to_integer_samples(gti.max() - gti.min(), dt)

flux_iterable1 = get_flux_iterable_from_segments(
times1, gti, segment_size, n_bin, dt=dt, fluxes=fluxes1, errors=errors1
times1, gti, segment_size, n_bin=n_bin, dt=dt, fluxes=fluxes1, errors=errors1
)
flux_iterable2 = get_flux_iterable_from_segments(
times2, gti, segment_size, n_bin, dt=dt, fluxes=fluxes2, errors=errors2
times2, gti, segment_size, n_bin=n_bin, dt=dt, fluxes=fluxes2, errors=errors2
)

is_events = np.all([val is None for val in (fluxes1, fluxes2, errors1, errors2)])
Expand Down
8 changes: 4 additions & 4 deletions stingray/powerspectrum.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

from .lightcurve import Lightcurve
from .fourier import avg_pds_from_iterable, unnormalize_periodograms
from .fourier import avg_pds_from_events
from .fourier import avg_pds_from_timeseries
from .fourier import get_flux_iterable_from_segments
from .fourier import poisson_level
from .fourier import get_rms_from_unnorm_periodogram
Expand Down Expand Up @@ -1134,7 +1134,7 @@ def powerspectrum_from_time_array(
force_averaged = segment_size is not None
# Suppress progress bar for single periodogram
silent = silent or (segment_size is None)
table = avg_pds_from_events(
table = avg_pds_from_timeseries(
times,
gti,
segment_size,
Expand Down Expand Up @@ -1267,7 +1267,7 @@ def powerspectrum_from_lightcurve(
if gti is None:
gti = lc.gti

table = avg_pds_from_events(
table = avg_pds_from_timeseries(
lc.time,
gti,
segment_size,
Expand Down Expand Up @@ -1347,7 +1347,7 @@ def powerspectrum_from_timeseries(
if error_flux_attr is not None:
err = getattr(ts, error_flux_attr)

results = avg_pds_from_events(
results = avg_pds_from_timeseries(
ts.time,
gti,
segment_size,
Expand Down
31 changes: 26 additions & 5 deletions stingray/tests/test_crossspectrum.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,19 @@ def setup_class(self):
)
self.lc1, self.lc2 = self.events1, self.events2

def test_single_cs_of_lc_with_tight_gtis_does_not_crash(self):
tstart = 1.0
tend = 10.0
gti = [[1.0, 9.0]]

time = np.linspace(tstart, tend, 10001)

counts1 = np.random.poisson(10, size=time.shape[0])
counts2 = np.random.poisson(10, size=time.shape[0])
lc1 = Lightcurve(time, counts1, gti=gti)
lc2 = Lightcurve(time, counts2, gti=gti)
Crossspectrum(lc1, lc2, norm="leahy")

@pytest.mark.parametrize("norm", ["leahy", "frac", "abs", "none"])
def test_common_mean_gives_comparable_scatter(self, norm):
acs = AveragedCrossspectrum(
Expand Down Expand Up @@ -704,14 +717,22 @@ def test_init_with_wrong_lc2_instance(self):

def test_make_crossspectrum_diff_lc_counts_shape(self):
counts = np.array([1] * 10001)
time = np.linspace(0.0, 1.0001, 10001)
lc_ = Lightcurve(time, counts)
dt = 0.0001
time = np.arange(0.0, 1.0001, dt)
lc_ = Lightcurve(time, counts, gti=[[time[0] - dt / 2, time[-1] + dt / 2]])
with pytest.warns(UserWarning, match="Lightcurves do not have same tseg"):
with pytest.raises(
AssertionError, match="No GTIs are equal to or longer than segment_size"
):
with pytest.raises(AssertionError, match="Time arrays are not the same"):
Crossspectrum(self.lc1, lc_)

def test_make_crossspectrum_lc_and_evts(self):
counts = np.array([1] * 10001)
dt = 0.0001
time = np.arange(0.0, 1.0001, dt)
lc_ = Lightcurve(time, counts, gti=[[time[0] - dt / 2, time[-1] + dt / 2]])
ev_ = EventList(time)
with pytest.raises(ValueError, match="Please use input data of the same kind"):
Crossspectrum(ev_, lc_, skip_checks=True)

def test_make_crossspectrum_diff_lc_stat(self):
lc_ = copy.deepcopy(self.lc1)
lc_.err_dist = "gauss"
Expand Down
Loading

0 comments on commit 5777271

Please sign in to comment.