Repository: stingraysoftware/stingray
Issue: `AveragedPowerspectrum.rebin()` doesn't work

using `rebin` on AveragedPowerspectrum returns the error:

```
TypeError                                 Traceback (most recent call last)
<ipython-input-12-d9030309c98d> in <module>()
----> 1 raps = aps.rebin(df=6, method='mean')

/home/evandromr/github/stingray/stingray/powerspectrum.py in rebin(self, df, method)
    172 
    173     def rebin(self, df, method="mean"):
--> 174         bin_ps = Crossspectrum.rebin(self, df=df, method=method)
    175         bin_ps.nphots = bin_ps.nphots1
    176 

/home/evandromr/github/stingray/stingray/crossspectrum.py in rebin(self, df, method)
    232         # make an empty cross spectrum object
    233         # note: syntax deliberate to work with subclass Powerspectrum
--> 234         bin_cs = self.__class__()
    235 
    236         # store the binned periodogram in the new object

TypeError: __init__() missing 2 required positional arguments: 'lc' and 'segment_size'
```

This happens because: 
1.  `AveragedPowerspectrum` requires `lc` and `segment_size` but `AveragedCrosspectrum` doesn't (default to `None`)
   - **Proposed Fix**: make `lc` and `segment_size` default to `None` in `AveragedPowerspectrum` and allow an 'empty' instantiation of the class.
2. `Crossspectrum.rebin`  tries to assign `nphots2`  which doesn't exist in `AveragedCrosspectrum`.
   - **Proposed Fix**: `Crosspectrum.rebin()` checks for the attribute `self.type` and assign either `self.nphots1` and `self.nphots2` if `self.type=='crosspectrum'`, or `self.nphots` if `selftype=="powerpsectrum"` (and raise `TypeError` if `self.type` is something else).

I can implement this fixes and also add one or two test for binning of AveragedPowerspectrum.
Until then, feel free to make comments on this or suggest a different approach to fix


Changed files:
stingray/powerspectrum.py, stingray/crossspectrum.py, stingray/tests/test_crossspectrum.py, stingray/tests/test_powerspectrum.py

diff --git a/stingray/crossspectrum.py b/stingray/crossspectrum.py
index b12484d6..0ff9a0ff 100644
--- a/stingray/crossspectrum.py
+++ b/stingray/crossspectrum.py
@@ -240,7 +240,14 @@ class Crossspectrum(object):
         bin_cs.n = self.n
         bin_cs.norm = self.norm
         bin_cs.nphots1 = self.nphots1
-        bin_cs.nphots2 = self.nphots2
+        try:
+            bin_cs.nphots2 = self.nphots2
+        except AttributeError:
+            if self.type == 'powerspectrum':
+                pass
+            else:
+                raise AttributeError('Spectrum has no attribute named nphots2.')
+
         bin_cs.m = int(step_size)*self.m
 
         return bin_cs
@@ -526,6 +533,9 @@ class AveragedCrossspectrum(Crossspectrum):
                 self.cs_all, nphots1_all = \\
                     self._make_segment_spectrum(lc1, self.segment_size)
 
+            else:
+                raise ValueError("Type of spectrum not recognized!")
+
         else:
             self.cs_all, nphots1_all, nphots2_all = [], [], []
             # TODO: should be using izip from iterables if lc1 or lc2 could
@@ -542,7 +552,7 @@ class AveragedCrossspectrum(Crossspectrum):
                         self._make_segment_spectrum(lc1_seg, self.segment_size)
 
                 else:
-                    raise Exception("Type of spectrum not recognized!")
+                    raise ValueError("Type of spectrum not recognized!")
 
                 self.cs_all.append(cs_sep)
                 nphots1_all.append(nphots1_sep)
diff --git a/stingray/powerspectrum.py b/stingray/powerspectrum.py
index 9bfaa117..9d6b9418 100755
--- a/stingray/powerspectrum.py
+++ b/stingray/powerspectrum.py
@@ -308,7 +308,7 @@ class Powerspectrum(Crossspectrum):
 
 class AveragedPowerspectrum(AveragedCrossspectrum, Powerspectrum):
 
-    def __init__(self, lc, segment_size, norm="frac", gti=None):
+    def __init__(self, lc=None, segment_size=None, norm="frac", gti=None):
         """
         Make an averaged periodogram from a light curve by segmenting the light
         curve, Fourier-transforming each segment and then averaging the
@@ -367,8 +367,9 @@ class AveragedPowerspectrum(AveragedCrossspectrum, Powerspectrum):
 
         self.type = "powerspectrum"
 
-        if not np.isfinite(segment_size):
-            raise ValueError("segment_size must be finite!")
+        if segment_size is not None:
+            if not np.isfinite(segment_size):
+                raise ValueError("segment_size must be finite!")
 
         self.segment_size = segment_size
 
diff --git a/stingray/tests/test_crossspectrum.py b/stingray/tests/test_crossspectrum.py
index 6f1bedfe..e5b76864 100644
--- a/stingray/tests/test_crossspectrum.py
+++ b/stingray/tests/test_crossspectrum.py
@@ -2,7 +2,7 @@ from __future__ import division
 import numpy as np
 import pytest
 import warnings
-from stingray import Lightcurve
+from stingray import Lightcurve, AveragedPowerspectrum
 from stingray import Crossspectrum, AveragedCrossspectrum, coherence
 from stingray import StingrayError
 
@@ -83,6 +83,11 @@ class TestCrossspectrum(object):
         with pytest.raises(TypeError):
             cs = Crossspectrum(self.lc1)
 
+    def test_init_with_multiple_gti(self):
+        gti = np.array([[0.0, 0.2], [0.6, 1.0]])
+        with pytest.raises(TypeError):
+            cs = Crossspectrum(self.lc1, self.lc2, gti=gti)
+
     def test_init_with_norm_not_str(self):
         with pytest.raises(TypeError):
             cs = Crossspectrum(norm=1)
@@ -180,6 +185,72 @@ class TestAveragedCrossspectrum(object):
 
         self.cs = AveragedCrossspectrum(self.lc1, self.lc2, segment_size=1)
 
+    def test_invalid_type_attribute(self):
+        with pytest.raises(ValueError):
+            cs_test = AveragedCrossspectrum(self.lc1, self.lc2, segment_size=1)
+            cs_test.type = 'invalid_type'
+            assert AveragedCrossspectrum._make_crossspectrum(cs_test,
+                                                             self.lc1,
+                                                             self.lc2)
+
+    def test_invalid_type_attribute_with_multiple_lcs(self):
+        acs_test = AveragedCrossspectrum([self.lc1, self.lc2],
+                                         [self.lc2, self.lc1],
+                                         segment_size=1)
+        acs_test.type = 'invalid_type'
+        with pytest.raises(ValueError):
+            assert AveragedCrossspectrum._make_crossspectrum(acs_test,
+                                                             lc1=[self.lc1,
+                                                                  self.lc2],
+                                                             lc2=[self.lc2,
+                                                                  self.lc1])
+
+    def test_different_dt(self):
+        time1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+        counts1_test = np.random.poisson(0.01, size=len(time1))
+        test_lc1 = Lightcurve(time1, counts1_test)
+
+        time2 = [2, 4, 6, 8, 10]
+        counts2_test = np.random.negative_binomial(1, 0.09, size=len(time2))
+        test_lc2 = Lightcurve(time2, counts2_test)
+
+        assert test_lc1.tseg == test_lc2.tseg
+
+        assert test_lc1.dt != test_lc2.dt
+
+        with pytest.raises(ValueError):
+            assert AveragedCrossspectrum(test_lc1, test_lc2, segment_size=1)
+
+    def test_different_tseg(self):
+        time2 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
+        counts2_test = np.random.poisson(0.01, size=len(time2))
+        test_lc2 = Lightcurve(time2, counts2_test)
+
+        time1 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+        counts1_test = np.random.negative_binomial(1, 0.09, size=len(time1))
+        test_lc1 = Lightcurve(time1, counts1_test)
+
+        assert test_lc2.dt == test_lc1.dt
+
+        assert test_lc2.tseg != test_lc1.tseg
+
+        with pytest.raises(ValueError):
+            assert AveragedCrossspectrum(test_lc1, test_lc2, segment_size=1)
+
+    def test_rebin_with_invalid_type_attribute(self):
+        new_df = 2
+        aps = AveragedPowerspectrum(lc=self.lc1, segment_size=1,
+                                    norm='leahy')
+        aps.type = 'invalid_type'
+        with pytest.raises(AttributeError):
+            assert aps.rebin(df=new_df)
+
+    def test_rebin_with_valid_type_attribute(self):
+        new_df = 2
+        aps = AveragedPowerspectrum(lc=self.lc1, segment_size=1,
+                                    norm='leahy')
+        assert aps.rebin(df=new_df)
+
     def test_init_with_norm_not_str(self):
         with pytest.raises(TypeError):
             cs = AveragedCrossspectrum(self.lc1, self.lc2, segment_size=1,
@@ -227,4 +298,4 @@ class TestAveragedCrossspectrum(object):
         with pytest.raises(ValueError):
             self.cs = AveragedCrossspectrum(self.lc1, self.lc2,
                                             segment_size=1,
-                                            norm="wrong")
\\ No newline at end of file
+                                            norm="wrong")
diff --git a/stingray/tests/test_powerspectrum.py b/stingray/tests/test_powerspectrum.py
index 426449b7..acf0fd2a 100644
--- a/stingray/tests/test_powerspectrum.py
+++ b/stingray/tests/test_powerspectrum.py
@@ -345,6 +345,33 @@ class TestAveragedPowerspectrum(object):
         segment_size = 0.5
         assert AveragedPowerspectrum(lc_all, segment_size)
 
+        def rebin_several_averagedps(self, df):
+            """
+            TODO: Not sure how to write tests for the rebin method!
+            """
+
+            aps = AveragedPowerspectrum(lc=self.lc, segment_size=1,
+                                        norm="Leahy")
+            bin_aps = aps.rebin(df)
+            assert np.isclose(bin_aps.freq[1]-bin_aps.freq[0], bin_aps.df,
+                              atol=1e-4, rtol=1e-4)
+            assert np.isclose(bin_aps.freq[0],
+                              (aps.freq[0]-aps.df*0.5+bin_aps.df*0.5),
+                              atol=1e-4, rtol=1e-4)
+
+        def test_rebin_averagedps(self):
+            df_all = [2, 3, 5, 1.5, 10, 45]
+            for df in df_all:
+                yield self.rebin_several, df
+
+        def test_rebin_with_invalid_type_attribute(self):
+            new_df = 2
+            aps = AveragedPowerspectrum(lc=self.lc, segment_size=1,
+                                        norm='leahy')
+            aps.type = 'invalid_type'
+            with pytest.raises(AttributeError):
+                assert aps.rebin(df=new_df)
+
     def test_list_with_nonsense_component(self):
         n_lcs = 10