From 6daa97c1cbe8ff702715c731a5535e39573a776c Mon Sep 17 00:00:00 2001
From: Moritz Huebner <moritz.huebner@ligo.org>
Date: Mon, 4 Feb 2019 22:40:41 -0600
Subject: [PATCH] Utils upgrade

---
 CHANGELOG.md                   |   2 +
 bilby/core/__init__.py         |   2 +-
 bilby/{gw => core}/series.py   |   6 +-
 bilby/core/utils.py            | 154 +++++++++++++++++++--------------
 bilby/gw/__init__.py           |   2 +-
 bilby/gw/detector.py           |   2 +-
 bilby/gw/waveform_generator.py |   2 +-
 test/detector_test.py          |   7 +-
 test/noise_realisation_test.py |  16 ++--
 test/series_test.py            |  63 ++++++++------
 test/utils_test.py             | 101 ++++++++++++++++++---
 11 files changed, 233 insertions(+), 124 deletions(-)
 rename bilby/{gw => core}/series.py (97%)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 593e16b1..f22560ff 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,8 @@
 - 
 
 ### Changed
+- Time/frequency array generation/conversion improved. We now impose `duration` is an integer multiple of
+  `sampling_frequency`. Converting back and forth between time/frequency arrays now works for all valid arrays.
 - Updates the bilby.core.utils constants to match those of Astropy v3.0.4
 
 ### Removed
diff --git a/bilby/core/__init__.py b/bilby/core/__init__.py
index daa0c453..9d1dd51e 100644
--- a/bilby/core/__init__.py
+++ b/bilby/core/__init__.py
@@ -1,2 +1,2 @@
 from __future__ import absolute_import
-from . import likelihood, prior, result, sampler, utils
+from . import likelihood, prior, result, sampler, series, utils
diff --git a/bilby/gw/series.py b/bilby/core/series.py
similarity index 97%
rename from bilby/gw/series.py
rename to bilby/core/series.py
index a83a0a65..980c6c11 100644
--- a/bilby/gw/series.py
+++ b/bilby/core/series.py
@@ -1,4 +1,4 @@
-from ..core import utils
+from . import utils
 
 
 class CoupledTimeAndFrequencySeries(object):
@@ -15,8 +15,8 @@ class CoupledTimeAndFrequencySeries(object):
     start_time: float, optional
         Starting time of the time array
         """
-        self.duration = duration
-        self.sampling_frequency = sampling_frequency
+        self._duration = duration
+        self._sampling_frequency = sampling_frequency
         self.start_time = start_time
         self._frequency_array_updated = False
         self._time_array_updated = False
diff --git a/bilby/core/utils.py b/bilby/core/utils.py
index ce634a6e..1b3b1cfa 100644
--- a/bilby/core/utils.py
+++ b/bilby/core/utils.py
@@ -20,6 +20,8 @@ parsec = 3.0856775814671916e+16  # m
 solar_mass = 1.9884754153381438e+30  # Kg
 radius_of_earth = 6378100.0  # m
 
+_TOL = 14
+
 
 def infer_parameters_from_function(func):
     """ Infers the arguments of a function
@@ -59,13 +61,18 @@ def _infer_args_from_function_except_for_first_arg(func):
     return parameters
 
 
-def get_sampling_frequency(time_series):
+def get_sampling_frequency(time_array):
     """
     Calculate sampling frequency from a time series
 
+    Attributes:
+    -------
+    time_array: array_like
+        Time array to get sampling_frequency from
+
     Returns
     -------
-    float: Sampling frequency of the time series
+    Sampling frequency of the time series: float
 
     Raises
     -------
@@ -73,19 +80,24 @@ def get_sampling_frequency(time_series):
 
     """
     tol = 1e-10
-    if np.ptp(np.diff(time_series)) > tol:
+    if np.ptp(np.diff(time_array)) > tol:
         raise ValueError("Your time series was not evenly sampled")
     else:
-        return 1. / (time_series[1] - time_series[0])
+        return np.round(1. / (time_array[1] - time_array[0]), decimals=_TOL)
 
 
 def get_sampling_frequency_and_duration_from_time_array(time_array):
     """
     Calculate sampling frequency and duration from a time array
 
+    Attributes:
+    -------
+    time_array: array_like
+        Time array to get sampling_frequency/duration from: array_like
+
     Returns
     -------
-    sampling_frequency, duration:
+    sampling_frequency, duration: float, float
 
     Raises
     -------
@@ -94,7 +106,7 @@ def get_sampling_frequency_and_duration_from_time_array(time_array):
     """
 
     sampling_frequency = get_sampling_frequency(time_array)
-    duration = time_array[-1] - time_array[0]
+    duration = len(time_array) / sampling_frequency
     return sampling_frequency, duration
 
 
@@ -102,9 +114,14 @@ def get_sampling_frequency_and_duration_from_frequency_array(frequency_array):
     """
     Calculate sampling frequency and duration from a frequency array
 
+    Attributes:
+    -------
+    frequency_array: array_like
+        Frequency array to get sampling_frequency/duration from: array_like
+
     Returns
     -------
-    sampling_frequency, duration:
+    sampling_frequency, duration: float, float
 
     Raises
     -------
@@ -118,8 +135,9 @@ def get_sampling_frequency_and_duration_from_frequency_array(frequency_array):
 
     number_of_frequencies = len(frequency_array)
     delta_freq = frequency_array[1] - frequency_array[0]
-    duration = 1 / delta_freq
-    sampling_frequency = 2 * number_of_frequencies / duration
+    duration = np.round(1 / delta_freq, decimals=_TOL)
+
+    sampling_frequency = np.round(2 * (number_of_frequencies - 1) / duration, decimals=14)
     return sampling_frequency, duration
 
 
@@ -137,7 +155,57 @@ def create_time_series(sampling_frequency, duration, starting_time=0.):
     float: An equidistant time series given the parameters
 
     """
-    return np.arange(starting_time, starting_time + duration, 1. / sampling_frequency)
+    _check_legal_sampling_frequency_and_duration(sampling_frequency, duration)
+    number_of_samples = int(duration * sampling_frequency)
+    return np.linspace(start=starting_time,
+                       stop=duration + starting_time - 1 / sampling_frequency,
+                       num=number_of_samples)
+
+
+def create_frequency_series(sampling_frequency, duration):
+    """ Create a frequency series with the correct length and spacing.
+
+    Parameters
+    -------
+    sampling_frequency: float
+    duration: float
+
+    Returns
+    -------
+    array_like: frequency series
+
+    """
+    _check_legal_sampling_frequency_and_duration(sampling_frequency, duration)
+    number_of_samples = int(np.round(duration * sampling_frequency))
+    number_of_frequencies = int(np.round(number_of_samples / 2) + 1)
+
+    return np.linspace(start=0,
+                       stop=sampling_frequency / 2,
+                       num=number_of_frequencies)
+
+
+def _check_legal_sampling_frequency_and_duration(sampling_frequency, duration):
+    """ By convention, sampling_frequency and duration have to multiply to an integer
+
+    This will check if the product of both parameters multiplies reasonably close
+    to an integer.
+
+    Parameters
+    -------
+    sampling_frequency: float
+    duration: float
+
+    """
+    num = sampling_frequency * duration
+    if np.abs(num - np.round(num)) > _TOL:
+        raise IllegalDurationAndSamplingFrequencyException(
+            '\nYour sampling frequency and duration must multiply to a number'
+            'up to (tol = {}) decimals close to an integer number. '
+            '\nBut sampling_frequency={} and  duration={} multiply to {}'.format(
+                _TOL, sampling_frequency, duration,
+                sampling_frequency * duration
+            )
+        )
 
 
 def ra_dec_to_theta_phi(ra, dec, gmst):
@@ -190,38 +258,6 @@ def gps_time_to_gmst(gps_time):
     return gmst
 
 
-def create_frequency_series(sampling_frequency, duration):
-    """ Create a frequency series with the correct length and spacing.
-
-    Parameters
-    -------
-    sampling_frequency: float
-    duration: float
-        duration of data
-
-    Returns
-    -------
-    array_like: frequency series
-
-    """
-    number_of_samples = duration * sampling_frequency
-    number_of_samples = int(np.round(number_of_samples))
-
-    # prepare for FFT
-    number_of_frequencies = (number_of_samples - 1) // 2
-    delta_freq = 1. / duration
-
-    frequencies = delta_freq * np.linspace(1, number_of_frequencies, number_of_frequencies)
-
-    if len(frequencies) % 2 == 1:
-        frequencies = np.concatenate(([0], frequencies, [sampling_frequency / 2.]))
-    else:
-        # no Nyquist frequency when N=odd
-        frequencies = np.concatenate(([0], frequencies))
-
-    return frequencies
-
-
 def create_white_noise(sampling_frequency, duration):
     """ Create white_noise which is then coloured by a given PSD
 
@@ -279,30 +315,18 @@ def nfft(time_domain_strain, sampling_frequency):
 
     Returns
     -------
-    frequency_domain_strain, frequency_array: (array, array)
+    frequency_domain_strain, frequency_array: (array_like, array_like)
         Single-sided FFT of time domain strain normalised to units of
         strain / Hz, and the associated frequency_array.
 
     """
-
-    if np.ndim(sampling_frequency) != 0:
-        raise ValueError("Sampling frequency must be interger or float")
-
-    # add one zero padding if time series doesn't have even number of samples
-    if np.mod(len(time_domain_strain), 2) == 1:
-        time_domain_strain = np.append(time_domain_strain, 0)
-    LL = len(time_domain_strain)
-    # frequency range
-    frequency_array = sampling_frequency / 2 * np.linspace(0, 1, int(LL / 2 + 1))
-
-    # calculate FFT
-    # rfft computes the fft for real inputs
     frequency_domain_strain = np.fft.rfft(time_domain_strain)
+    frequency_domain_strain /= sampling_frequency
 
-    # normalise to units of strain / Hz
-    norm_frequency_domain_strain = frequency_domain_strain / sampling_frequency
+    frequency_array = np.linspace(
+        0, sampling_frequency / 2, len(frequency_domain_strain))
 
-    return norm_frequency_domain_strain, frequency_array
+    return frequency_domain_strain, frequency_array
 
 
 def infft(frequency_domain_strain, sampling_frequency):
@@ -313,18 +337,14 @@ def infft(frequency_domain_strain, sampling_frequency):
     frequency_domain_strain: array_like
         Single-sided, normalised FFT of the time-domain strain data (in units
         of strain / Hz).
-    sampling_frequency: float
+    sampling_frequency: int, float
         Sampling frequency of the data.
 
     Returns
     -------
-    time_domain_strain: array
+    time_domain_strain: array_like
         An array of the time domain strain
     """
-
-    if np.ndim(sampling_frequency) != 0:
-        raise ValueError("Sampling frequency must be integer or float")
-
     time_domain_strain_norm = np.fft.irfft(frequency_domain_strain)
     time_domain_strain = time_domain_strain_norm * sampling_frequency
     return time_domain_strain
@@ -729,3 +749,7 @@ else:
             break
         except Exception:
             print(traceback.format_exc())
+
+
+class IllegalDurationAndSamplingFrequencyException(Exception):
+    pass
diff --git a/bilby/gw/__init__.py b/bilby/gw/__init__.py
index b76318a7..75e3a380 100644
--- a/bilby/gw/__init__.py
+++ b/bilby/gw/__init__.py
@@ -1,4 +1,4 @@
-from . import (calibration, conversion, detector, likelihood, prior, series, source,
+from . import (calibration, conversion, detector, likelihood, prior, source,
                utils, waveform_generator, result)
 
 from .waveform_generator import WaveformGenerator
diff --git a/bilby/gw/detector.py b/bilby/gw/detector.py
index 599f4924..38db420b 100644
--- a/bilby/gw/detector.py
+++ b/bilby/gw/detector.py
@@ -12,8 +12,8 @@ import deepdish as dd
 from . import utils as gwutils
 from ..core import utils
 from ..core.utils import logger
+from ..core.series import CoupledTimeAndFrequencySeries
 from .calibration import Recalibrate
-from .series import CoupledTimeAndFrequencySeries
 
 try:
     import gwpy
diff --git a/bilby/gw/waveform_generator.py b/bilby/gw/waveform_generator.py
index 6e9a5f6a..00e24bde 100644
--- a/bilby/gw/waveform_generator.py
+++ b/bilby/gw/waveform_generator.py
@@ -1,7 +1,7 @@
 import numpy as np
 
 from ..core import utils
-from ..gw.series import CoupledTimeAndFrequencySeries
+from ..core.series import CoupledTimeAndFrequencySeries
 
 
 class WaveformGenerator(object):
diff --git a/test/detector_test.py b/test/detector_test.py
index 527f1e74..95b2f5f3 100644
--- a/test/detector_test.py
+++ b/test/detector_test.py
@@ -545,7 +545,8 @@ class TestInterferometerStrainData(unittest.TestCase):
         sampling_frequency = 10
         time_array = bilby.core.utils.create_time_series(
             sampling_frequency=sampling_frequency, duration=duration)
-        time_domain_strain = np.random.normal(0, 1, len(time_array))
+        time_domain_strain = np.random.normal(
+            0, duration - 1 / sampling_frequency, len(time_array))
         self.ifosd.roll_off = 0
         self.ifosd.set_from_time_domain_strain(
             time_domain_strain=time_domain_strain, duration=duration,
@@ -711,11 +712,11 @@ class TestInterferometerStrainDataEquals(unittest.TestCase):
         self.assertNotEqual(self.ifosd_1, self.ifosd_2)
 
     def test_eq_different_sampling_frequency(self):
-        self.ifosd_1.sampling_frequency -= 0.1
+        self.ifosd_1.sampling_frequency *= 2
         self.assertNotEqual(self.ifosd_1, self.ifosd_2)
 
     def test_eq_different_sampling_duration(self):
-        self.ifosd_1.duration -= 0.1
+        self.ifosd_1.duration *= 2
         self.assertNotEqual(self.ifosd_1, self.ifosd_2)
 
     def test_eq_different_start_time(self):
diff --git a/test/noise_realisation_test.py b/test/noise_realisation_test.py
index 026af6d0..15aa404f 100644
--- a/test/noise_realisation_test.py
+++ b/test/noise_realisation_test.py
@@ -22,27 +22,25 @@ class TestNoiseRealisation(unittest.TestCase):
 
         a = np.nan_to_num(interferometer.amplitude_spectral_density_array / factor * interferometer.frequency_mask)
         b = asd_avg
-        print(a, b)
         self.assertTrue(np.allclose(a, b, rtol=1e-1))
 
     def test_noise_normalisation(self):
-        time_duration = 1.
+        duration = 1.
         sampling_frequency = 4096.
-        time_array = bilby.core.utils.create_time_series(sampling_frequency=sampling_frequency, duration=time_duration)
+        time_array = bilby.core.utils.create_time_series(
+            sampling_frequency=sampling_frequency, duration=duration)
 
         # generate some toy-model signal for matched filtering SNR testing
         n_avg = 1000
         snr = np.zeros(n_avg)
-        mu = np.exp(-(time_array-time_duration/2.)**2 / (2.*0.1**2)) * np.sin(2*np.pi*100*time_array)
+        mu = (np.exp(-(time_array-duration/2.)**2 / (2.*0.1**2))
+              * np.sin(2 * np.pi * 100 * time_array))
         muf, frequency_array = bilby.core.utils.nfft(mu, sampling_frequency)
         for x in range(0, n_avg):
             interferometer = bilby.gw.detector.get_empty_interferometer('H1')
             interferometer.set_strain_data_from_power_spectral_density(
-                sampling_frequency=sampling_frequency, duration=time_duration)
-            hf_tmp = interferometer.strain_data.frequency_domain_strain
-            psd = interferometer.power_spectral_density
-            snr[x] = bilby.gw.utils.inner_product(hf_tmp, muf, frequency_array, psd) \
-                     / np.sqrt(bilby.gw.utils.inner_product(muf, muf, frequency_array, psd))
+                sampling_frequency=sampling_frequency, duration=duration)
+            snr[x] = interferometer.matched_filter_snr(signal=muf)
 
         self.assertTrue(np.isclose(np.std(snr), 1.00, atol=1e-1))
 
diff --git a/test/series_test.py b/test/series_test.py
index 685164ab..1eeec8ca 100644
--- a/test/series_test.py
+++ b/test/series_test.py
@@ -1,8 +1,10 @@
 from __future__ import absolute_import
 import unittest
-import bilby
 import numpy as np
 
+from bilby.core.utils import create_frequency_series, create_time_series
+from bilby.core.series import CoupledTimeAndFrequencySeries
+
 
 class TestCoupledTimeAndFrequencySeries(unittest.TestCase):
 
@@ -10,9 +12,9 @@ class TestCoupledTimeAndFrequencySeries(unittest.TestCase):
         self.duration = 2
         self.sampling_frequency = 4096
         self.start_time = -1
-        self.series = bilby.gw.series.CoupledTimeAndFrequencySeries(duration=self.duration,
-                                                                    sampling_frequency=self.sampling_frequency,
-                                                                    start_time=self.start_time)
+        self.series = CoupledTimeAndFrequencySeries(
+            duration=self.duration, sampling_frequency=self.sampling_frequency,
+            start_time=self.start_time)
 
     def tearDown(self):
         del self.duration
@@ -21,17 +23,19 @@ class TestCoupledTimeAndFrequencySeries(unittest.TestCase):
         del self.series
 
     def test_repr(self):
-        expected = 'CoupledTimeAndFrequencySeries(duration={}, sampling_frequency={}, start_time={})'\
-            .format(self.series.duration,
-                    self.series.sampling_frequency,
-                    self.series.start_time)
+        expected =\
+            'CoupledTimeAndFrequencySeries(duration={}, sampling_frequency={},'\
+            ' start_time={})'.format(
+                self.series.duration, self.series.sampling_frequency,
+                self.series.start_time)
         self.assertEqual(expected, repr(self.series))
 
     def test_duration_from_init(self):
         self.assertEqual(self.duration, self.series.duration)
 
     def test_sampling_from_init(self):
-        self.assertEqual(self.sampling_frequency, self.series.sampling_frequency)
+        self.assertEqual(
+            self.sampling_frequency, self.series.sampling_frequency)
 
     def test_start_time_from_init(self):
         self.assertEqual(self.start_time, self.series.start_time)
@@ -43,24 +47,26 @@ class TestCoupledTimeAndFrequencySeries(unittest.TestCase):
         self.assertIsInstance(self.series.time_array, np.ndarray)
 
     def test_frequency_array_from_init(self):
-        expected = bilby.core.utils.create_frequency_series(sampling_frequency=self.sampling_frequency,
-                                                            duration=self.duration)
+        expected = create_frequency_series(
+            sampling_frequency=self.sampling_frequency, duration=self.duration)
         self.assertTrue(np.array_equal(expected, self.series.frequency_array))
 
     def test_time_array_from_init(self):
-        expected = bilby.core.utils.create_time_series(sampling_frequency=self.sampling_frequency,
-                                                       duration=self.duration,
-                                                       starting_time=self.start_time)
+        expected = create_time_series(
+            sampling_frequency=self.sampling_frequency, duration=self.duration,
+            starting_time=self.start_time)
         self.assertTrue(np.array_equal(expected, self.series.time_array))
 
     def test_frequency_array_setter(self):
         new_sampling_frequency = 100
         new_duration = 3
-        new_frequency_array = bilby.core.utils.create_frequency_series(sampling_frequency=new_sampling_frequency,
-                                                                       duration=new_duration)
+        new_frequency_array = create_frequency_series(
+            sampling_frequency=new_sampling_frequency, duration=new_duration)
         self.series.frequency_array = new_frequency_array
-        self.assertTrue(np.array_equal(new_frequency_array, self.series.frequency_array))
-        self.assertLessEqual(np.abs(new_sampling_frequency - self.series.sampling_frequency), 1)
+        self.assertTrue(np.array_equal(
+            new_frequency_array, self.series.frequency_array))
+        self.assertLessEqual(np.abs(
+            new_sampling_frequency - self.series.sampling_frequency), 1)
         self.assertAlmostEqual(new_duration, self.series.duration)
         self.assertAlmostEqual(self.start_time, self.series.start_time)
 
@@ -68,12 +74,13 @@ class TestCoupledTimeAndFrequencySeries(unittest.TestCase):
         new_sampling_frequency = 100
         new_duration = 3
         new_start_time = 4
-        new_time_array = bilby.core.utils.create_time_series(sampling_frequency=new_sampling_frequency,
-                                                             duration=new_duration,
-                                                             starting_time=new_start_time)
+        new_time_array = create_time_series(
+            sampling_frequency=new_sampling_frequency, duration=new_duration,
+            starting_time=new_start_time)
         self.series.time_array = new_time_array
         self.assertTrue(np.array_equal(new_time_array, self.series.time_array))
-        self.assertAlmostEqual(new_sampling_frequency, self.series.sampling_frequency, places=1)
+        self.assertAlmostEqual(
+            new_sampling_frequency, self.series.sampling_frequency, places=1)
         self.assertAlmostEqual(new_duration, self.series.duration, places=1)
         self.assertAlmostEqual(new_start_time, self.series.start_time, places=1)
 
@@ -81,22 +88,26 @@ class TestCoupledTimeAndFrequencySeries(unittest.TestCase):
         self.series.sampling_frequency = None
         self.series.duration = 4
         with self.assertRaises(ValueError):
-            test = self.series.time_array
+            _ = self.series.time_array
 
     def test_time_array_without_duration(self):
         self.series.sampling_frequency = 4096
         self.series.duration = None
         with self.assertRaises(ValueError):
-            test = self.series.time_array
+            _ = self.series.time_array
 
     def test_frequency_array_without_sampling_frequency(self):
         self.series.sampling_frequency = None
         self.series.duration = 4
         with self.assertRaises(ValueError):
-            test = self.series.frequency_array
+            _ = self.series.frequency_array
 
     def test_frequency_array_without_duration(self):
         self.series.sampling_frequency = 4096
         self.series.duration = None
         with self.assertRaises(ValueError):
-            test = self.series.frequency_array
+            _ = self.series.frequency_array
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/test/utils_test.py b/test/utils_test.py
index ad1d2278..3b5645c8 100644
--- a/test/utils_test.py
+++ b/test/utils_test.py
@@ -26,25 +26,27 @@ class TestConstants(unittest.TestCase):
 class TestFFT(unittest.TestCase):
 
     def setUp(self):
-        pass
+        self.sampling_frequency = 10
 
     def tearDown(self):
-        pass
+        del self.sampling_frequency
 
-    def test_nfft_frequencies(self):
-        f = 2.1
-        sampling_frequency = 10
-        times = np.arange(0, 100, 1/sampling_frequency)
-        tds = np.sin(2*np.pi*times * f + 0.4)
-        fds, freqs = bilby.core.utils.nfft(tds, sampling_frequency)
-        self.assertTrue(np.abs((f-freqs[np.argmax(np.abs(fds))])/f < 1e-15))
+    def test_nfft_sine_function(self):
+        injected_frequency = 2.7324
+        duration = 100
+        times = utils.create_time_series(self.sampling_frequency, duration)
+
+        time_domain_strain = np.sin(2 * np.pi * times * injected_frequency + 0.4)
+
+        frequency_domain_strain, frequencies = bilby.core.utils.nfft(time_domain_strain, self.sampling_frequency)
+        frequency_at_peak = frequencies[np.argmax(np.abs(frequency_domain_strain))]
+        self.assertAlmostEqual(injected_frequency, frequency_at_peak, places=1)
 
     def test_nfft_infft(self):
-        sampling_frequency = 10
-        tds = np.random.normal(0, 1, 10)
-        fds, _ = bilby.core.utils.nfft(tds, sampling_frequency)
-        tds2 = bilby.core.utils.infft(fds, sampling_frequency)
-        self.assertTrue(np.all(np.abs((tds - tds2) / tds) < 1e-12))
+        time_domain_strain = np.random.normal(0, 1, 10)
+        frequency_domain_strain, _ = bilby.core.utils.nfft(time_domain_strain, self.sampling_frequency)
+        new_time_domain_strain = bilby.core.utils.infft(frequency_domain_strain, self.sampling_frequency)
+        self.assertTrue(np.allclose(time_domain_strain, new_time_domain_strain))
 
 
 class TestInferParameters(unittest.TestCase):
@@ -76,5 +78,76 @@ class TestInferParameters(unittest.TestCase):
         self.assertListEqual(expected, actual)
 
 
+class TestTimeAndFrequencyArrays(unittest.TestCase):
+
+    def setUp(self):
+        self.start_time = 1.3
+        self.sampling_frequency = 5
+        self.duration = 1.6
+        self.frequency_array = utils.create_frequency_series(sampling_frequency=self.sampling_frequency,
+                                                             duration=self.duration)
+        self.time_array = utils.create_time_series(sampling_frequency=self.sampling_frequency,
+                                                   duration=self.duration,
+                                                   starting_time=self.start_time)
+
+    def tearDown(self):
+        del self.start_time
+        del self.sampling_frequency
+        del self.duration
+        del self.frequency_array
+        del self.time_array
+
+    def test_create_time_array(self):
+        expected_time_array = np.array([1.3, 1.5, 1.7, 1.9, 2.1, 2.3, 2.5, 2.7])
+        time_array = utils.create_time_series(sampling_frequency=self.sampling_frequency,
+                                              duration=self.duration, starting_time=self.start_time)
+        self.assertTrue(np.allclose(expected_time_array, time_array))
+
+    def test_create_frequency_array(self):
+        expected_frequency_array = np.array([0.0, 0.625, 1.25, 1.875, 2.5])
+        frequency_array = utils.create_frequency_series(sampling_frequency=self.sampling_frequency,
+                                                        duration=self.duration)
+        self.assertTrue(np.allclose(expected_frequency_array, frequency_array))
+
+    def test_get_sampling_frequency_from_time_array(self):
+        new_sampling_freq, _ = utils.get_sampling_frequency_and_duration_from_time_array(
+            self.time_array)
+        self.assertEqual(self.sampling_frequency, new_sampling_freq)
+
+    def test_get_duration_from_time_array(self):
+        _, new_duration = utils.get_sampling_frequency_and_duration_from_time_array(self.time_array)
+        self.assertEqual(self.duration, new_duration)
+
+    def test_get_start_time_from_time_array(self):
+        new_start_time = self.time_array[0]
+        self.assertEqual(self.start_time, new_start_time)
+
+    def test_get_sampling_frequency_from_frequency_array(self):
+        new_sampling_freq, _ = utils.get_sampling_frequency_and_duration_from_frequency_array(
+            self.frequency_array)
+        self.assertEqual(self.sampling_frequency, new_sampling_freq)
+
+    def test_get_duration_from_frequency_array(self):
+        _, new_duration = utils.get_sampling_frequency_and_duration_from_frequency_array(
+            self.frequency_array)
+        self.assertEqual(self.duration, new_duration)
+
+    def test_consistency_time_array_to_time_array(self):
+        new_sampling_frequency, new_duration = \
+            utils.get_sampling_frequency_and_duration_from_time_array(self.time_array)
+        new_start_time = self.time_array[0]
+        new_time_array = utils.create_time_series(sampling_frequency=new_sampling_frequency,
+                                                  duration=new_duration,
+                                                  starting_time=new_start_time)
+        self.assertTrue(np.allclose(self.time_array, new_time_array))
+
+    def test_consistency_frequency_array_to_frequency_array(self):
+        new_sampling_frequency, new_duration = utils.get_sampling_frequency_and_duration_from_frequency_array(self.frequency_array)
+        new_frequency_array = \
+            utils.create_frequency_series(sampling_frequency=new_sampling_frequency,
+                                          duration=new_duration)
+        self.assertTrue(np.allclose(self.frequency_array, new_frequency_array))
+
+
 if __name__ == '__main__':
     unittest.main()
-- 
GitLab