diff --git a/tftb/__init__.py b/tftb/__init__.py index 02a6daa..2c5fb2d 100644 --- a/tftb/__init__.py +++ b/tftb/__init__.py @@ -1,4 +1,3 @@ -import tftb.tests -import tftb.processing -import tftb.generators +from tftb import generators, processing, utils +__all__ = ["generators", "processing", "utils"] diff --git a/tftb/generators/__init__.py b/tftb/generators/__init__.py index 234f83f..37f937a 100644 --- a/tftb/generators/__init__.py +++ b/tftb/generators/__init__.py @@ -1,11 +1,11 @@ -from .amplitude_modulated import amexpos, amgauss, amrect, amtriang -from .frequency_modulated import (fmconst, fmhyp, fmlin, fmodany, fmpar, +from amplitude_modulated import amexpos, amgauss, amrect, amtriang +from frequency_modulated import (fmconst, fmhyp, fmlin, fmodany, fmpar, fmpower, fmsin) -from .utils import sigmerge, scale -from .noise import dopnoise, noisecg, noisecu -from .analytic_signals import (anaask, anabpsk, anafsk, anapulse, anaqpsk, +from utils import sigmerge, scale +from noise import dopnoise, noisecg, noisecu +from analytic_signals import (anaask, anabpsk, anafsk, anapulse, anaqpsk, anasing, anastep) -from tftb.generators.misc import doppler, gdpower, klauder, mexhat, altes, atoms +from misc import doppler, gdpower, klauder, mexhat, altes, atoms __all__ = ['amexpos', 'amgauss', 'amrect', 'amtriang', 'fmconst', 'fmhyp', 'fmlin', 'fmodany', 'fmpar', 'fmpower', 'fmsin', 'sigmerge', diff --git a/tftb/generators/analytic_signals.py b/tftb/generators/analytic_signals.py index bc0bc2a..3f38912 100644 --- a/tftb/generators/analytic_signals.py +++ b/tftb/generators/analytic_signals.py @@ -1,13 +1,9 @@ import numpy as np from numpy import pi from scipy.signal import hilbert -# -# package structure may need to be re-evaluated via the init.py file(s), so it is available as: -# tftb.generators.fmconst -# fmconst is imported in __init__.py; what's the right way to import this for this module in the package? -#from .frequency_modulated import fmconst from tftb.generators import fmconst + def anaask(n_points, n_comp=None, f0=0.25): """Generate an amplitude shift (ASK) keying signal. @@ -30,8 +26,7 @@ def anaask(n_points, n_comp=None, f0=0.25): n_comp = np.round(n_points / 2) if (f0 < 0) or (f0 > 0.5): raise TypeError("f0 must be between 0 and 0.5") - #m = np.ceil(n_points / n_comp) - m = int(np.ceil(n_points / n_comp)) # getting an int deprication warning... + m = int(np.ceil(n_points / n_comp)) jumps = np.random.rand(m) am = np.kron(jumps, np.ones((n_comp,)))[:n_points] fm, iflaw = fmconst(n_points, f0, 1) @@ -61,7 +56,6 @@ def anabpsk(n_points, n_comp=None, f0=0.25): n_comp = np.round(n_points / 5) if (f0 < 0) or (f0 > 0.5): raise TypeError("f0 must be between 0 and 0.5") - #m = np.ceil(n_points / n_comp) m = int(np.ceil(n_points / n_comp)) jumps = 2.0 * np.round(np.random.rand(m)) - 1 am = np.kron(jumps, np.ones((n_comp,)))[:n_points] @@ -143,7 +137,6 @@ def anaqpsk(n_points, n_comp=None, f0=0.25): n_comp = np.round(n_points / 5) if (f0 < 0) or (f0 > 0.5): raise TypeError("f0 must be between 0 and 0.5") - #m = np.ceil(n_points / n_comp) m = int(np.ceil(n_points / n_comp)) jumps = np.floor(4 * np.random.rand(m)) jumps[jumps == 4] = 3 diff --git a/tftb/generators/misc.py b/tftb/generators/misc.py index 41e1ce1..aecfa9c 100644 --- a/tftb/generators/misc.py +++ b/tftb/generators/misc.py @@ -54,16 +54,8 @@ def atoms(n_points, coordinates): .. plot:: docstring_plots/generators/misc/atoms.py """ - # FIXME: This function produces incorrect output when coordinates are - # one-dimensional. - # yoder: would the fix be something like: if not isinstance(coordinates[0], list): coordinates = [coordinates] - # ??? - # at first glance, it looks ok. let's just add some data typ handlers. they may be imperfect, but the basic idea is to catch - # some common errors, like passing a list, not an array. - # an un-contained 1-d coordinates array: - if not hasattr(coordinates[0], '__len__'): coordinates = [coordinates] - if not hasattr(coordinates, 'shape'): coordinates = numpy.array(coordinates) - # + if coordinates.ndim == 1: + coordinates = coordinates.reshape((coordinates.shape[0], 1)) signal = np.zeros((n_points,), dtype=complex) n_atoms = coordinates.shape[0] for k in range(n_atoms): @@ -147,9 +139,6 @@ def klauder(n_points, attenuation=10.0, f0=0.2): assert n_points > 0 assert ((f0 < 0.5) and (f0 > 0)) - # - # debug: - #print('debug: klauder:: n_points=', n_points) f = np.linspace(0., 0.5, int(n_points / 2 + 1)) mod = np.exp(-2. * np.pi * attenuation * f) * f ** (2. * np.pi * attenuation * f0 - 0.5) @@ -198,17 +187,13 @@ def gdpower(n_points, degree=0.0, rate=1.0): frequency bins. :rtype: tuple """ - # quickly, handle types: n_points = int(n_points) - degree = float(degree) - rate = float(rate) - #print("diagnostic: running gdpower") + degree = float(degree) + rate = float(rate) t0 = 0 - #lnu = int(np.round(n_points / 2)) - lnu = int(np.ceil(n_points / 2)) # ?? + lnu = int(np.ceil(n_points / 2)) nu = np.linspace(0, 0.5, lnu + 1) nu = nu[1:] - #am = nu ** ((degree - 2) / 6) am = nu ** ((degree - 2.0) / 6.0) if rate == 0.: @@ -217,11 +202,9 @@ def gdpower(n_points, degree=0.0, rate=1.0): tfx = np.zeros((n_points,), dtype=complex) if (degree < 1.) and (degree != 0): - #d = n_points ** (degree * rate) d = float(n_points) ** (degree * rate) t0 = n_points / 10.0 - #tfx[:lnu] = np.exp(-1j * 2 * pi * (t0 * nu + d * nu ** degree / degree)) * am - tfx[:lnu] = np.exp(-1.0j * 2. * pi * ((t0 * nu + d * nu ** degree) / degree)) * am + tfx[:lnu] = np.exp(-1.0j * 2. * pi * (t0 * nu + d * nu ** degree / degree)) * am x = np.fft.ifft(tfx) elif degree == 0: d = rate @@ -229,27 +212,22 @@ def gdpower(n_points, degree=0.0, rate=1.0): tfx[:lnu] = np.exp(-1j * 2 * np.pi * (t0 * nu + d * np.log(nu))) * am x = np.fft.ifft(tfx) elif degree == 1.: - from .analytic_signals import anapulse + from analytic_signals import anapulse t0 = n_points x = anapulse(n_points, t0) elif degree > 1.: - #d = n_points * 2 ** (degree - 1) * rate d = n_points * 2. ** (degree - 1.) * rate - #tfx[:lnu] = np.exp(-1j * 2 * pi * (t0 * nu + d * nu ** degree / degree)) * am - tfx[:lnu] = np.exp(-1.0j * 2.0 * pi * ((t0 * nu + d * nu ** degree) / degree)) * am + tfx[:lnu] = np.exp(-1.0j * 2.0 * pi * (t0 * nu + d * nu ** degree / degree)) * am x = np.fft.ifft(tfx) else: t0 = n_points / 10 - #d = n_points * 2 ** (degree - 1) * rate d = n_points * 2.0 ** (degree - 1.0) * rate tfx[:lnu] = np.exp(-1j * 2 * pi * (t0 * nu + d * np.log(nu))) * am x = np.fft.ifft(tfx) if degree != 1.0: - #gpd = t0 + np.abs(np.sign(rate) - 1) / 2 * (n_points + 1) + d * nu ** (degree - 1) gpd = t0 + np.abs(np.sign(rate) - 1.0) / 2.0 * (n_points + 1.0) + d * nu ** (degree - 1.0) else: - #gpd = t0 * np.ones((n_points / 2,)) gpd = t0 * np.ones((n_points / 2.0,)) x = x - x.mean() diff --git a/tftb/generators/noise.py b/tftb/generators/noise.py index cac3a66..cbd4508 100644 --- a/tftb/generators/noise.py +++ b/tftb/generators/noise.py @@ -24,9 +24,7 @@ def noisecu(n_points): if n_points <= 2: noise = (np.random.rand(n_points, 1) - 0.5 + 1j * (np.random.rand(n_points, 1) - 0.5)) * np.sqrt(6) else: - # getting a non-integer warning, promising an error in time to come, here... noise = np.random.rand(2 ** int(nextpow2(n_points)),) - 0.5 - #noise = np.random.rand(2 ** nextpow2(n_points),) - 0.5 noise = hilbert(noise) / noise.std() / np.sqrt(2) inds = noise.shape[0] - np.arange(n_points - 1, -1, step=-1) - 1 noise = noise[inds] @@ -62,8 +60,7 @@ def noisecg(n_points, a1=None, a2=None): if n_points <= 2: noise = (np.random.randn(n_points, 1.) + 1j * np.random.randn(n_points, 1.)) / np.sqrt(2.) else: - #noise = np.random.normal(size=(2 ** nextpow2(n_points),)) - noise = np.random.normal(size=int(2. ** nextpow2(float(n_points)),)) # handle floats/ints a bit more carefully. + noise = np.random.normal(size=int(2. ** nextpow2(float(n_points)),)) noise = hilbert(noise) / noise.std() / np.sqrt(2.) noise = noise[len(noise) - np.arange(n_points - 1, -1, -1) - 1] return noise diff --git a/tftb/tests/__init__.py b/tftb/tests/__init__.py index 5938b11..e69de29 100644 --- a/tftb/tests/__init__.py +++ b/tftb/tests/__init__.py @@ -1,5 +0,0 @@ -from . import * -import glob # file listing utility. -import os -# -__all__ = [os.path.splitext(fl)[0] for fl in glob.glob('*.py')] diff --git a/tftb/tests/base.py b/tftb/tests/base.py index 8ca8bef..c45b45b 100644 --- a/tftb/tests/base.py +++ b/tftb/tests/base.py @@ -8,34 +8,24 @@ """Base class for tests.""" +import sys import unittest import numpy as np from scipy import angle from tftb.utils import is_linear -# yoder: -# let's add at least some backwards python2.x compatibility for now. -import sys -#py_ver = sys.version_info.major -# and assume only backwards revisions (for now): -ispy2 = (sys.version_info.major<3) # maybe we should work this into TestBase? +ispy2 = sys.version_info.major < 3 + class TestBase(unittest.TestCase): - # yoder: add __init__() - def __init__(self, *args, **kwargs): - # handle various bits, including some python2-3 compatibility, then execute base __init__ as super() - if not ispy2: - # re-map some function calls: - self.assertItemsEqual = self.assertCountEqual #(inherited from unittest.TestCase) - # ... and others... - # - # and let's go backwards as well, just to be sure (now, we can correct all the downstream code and remove this hack at a later time...). - if ispy2: - self.assertCountEqual = self.assertItemsEqual - # - super(TestBase,self).__init__(*args, **kwargs) - + @classmethod + def setUpClass(cls): + if ispy2: + cls.assertCountEqual = cls.assertItemsEqual + else: + cls.assertItemsEqual = cls.assertCountEqual + def assert_is_linear(self, signal, decimals=5): """Assert that the signal is linear.""" self.assertTrue(is_linear(signal, decimals=decimals)) diff --git a/tftb/tests/test_amplitude_modulations.py b/tftb/tests/test_amplitude_modulations.py index 05b8743..e07ddfb 100644 --- a/tftb/tests/test_amplitude_modulations.py +++ b/tftb/tests/test_amplitude_modulations.py @@ -2,9 +2,6 @@ import numpy as np from numpy import pi from scipy.signal import argrelmax -# diagnostics: -import base # this would be the 'local' import, so it will depend on the local directory structure. nominally, if we properly configure setup.py - # and install, we should be able to use the line below. are tests meant to be part of the package, or run separately? from tftb.tests.base import TestBase import tftb.generators.amplitude_modulated as am diff --git a/tftb/tests/test_analytic_signals.py b/tftb/tests/test_analytic_signals.py index 0398ecc..db3e03b 100644 --- a/tftb/tests/test_analytic_signals.py +++ b/tftb/tests/test_analytic_signals.py @@ -13,13 +13,6 @@ import numpy as np from tftb.tests.base import TestBase from tftb.generators import analytic_signals as ana -# -# let's add at least some backwards python2.x compatibility for now. -# (moving this to the unit test class definition (see tests/base.py) -#import sys -#py_ver = sys.version_info.major -# and assume only backwards revisions (for now): -#ispy2 = (sys.version_info.major<3) class TestAnalyticSignals(TestBase): @@ -33,10 +26,7 @@ def test_anaask(self): def test_anabpsk(self): """Test analytic BPSK signal.""" signal, amlaw = ana.anabpsk(300, 30, 0.1) - # - #self.assertItemsEqual(np.unique(amlaw), [-1, 1]) # python2.x syntax (py2/3 cases should be handled in TestBase class definition). self.assertCountEqual(np.unique(amlaw), [-1, 1]) - # self.assert_is_analytic(signal) def test_anafsk(self): diff --git a/tftb/tests/test_misc.py b/tftb/tests/test_misc.py index 43631a7..dccf309 100644 --- a/tftb/tests/test_misc.py +++ b/tftb/tests/test_misc.py @@ -15,14 +15,6 @@ import numpy as np from scipy.signal import argrelmax, argrelmin, hanning -#import pylab as plt - -# yoder: -# let's add at least some backwards python2.x compatibility for now. -import sys -#py_ver = sys.version_info.major -# and assume only backwards revisions (for now): -#ispy2 = (sys.version_info.major<3) # moved version compatibility into base class: TestBase()? class TestMisc(TestBase): @@ -58,12 +50,6 @@ def test_mexhat(self): self.assertEqual(maxima[0].shape[0], 1) self.assertEqual(maxima[0][0], 3) minima = argrelmin(actual) - # - # handling 2/3 compatibility in the class definition (see base.py) - #self.assertEqual(minima[0].shape[0], 2) - #if ispy2: - # self.assertItemsEqual(minima[0], (2, 4)) - #else: self.assertCountEqual(minima[0], (2, 4)) def test_gdpower(self): @@ -71,25 +57,14 @@ def test_gdpower(self): -0.08825763 + 0.17010894j, 0.04412953 - 0.01981114j, -0.04981628 + 0.34985966j, -0.56798889 - 0.07983783j, 0.05266730 - 0.57074006j, 0.35650159 - 0.01577918j]) - # ideal_f = np.array([0.125, 0.25, 0.375, 0.5]) ideal_gpd = np.array([8.8, 6.45685425, 5.41880215, 4.8]) ideals = (ideal_sig, ideal_gpd, ideal_f) - # - # let's be a bit smarter about this: - #actuals = misc.gdpower(8, 0.5) actuals = misc.gdpower(len(ideal_sig), 0.5) - # for i, ideal in enumerate(ideals): actual = actuals[i] - # np.testing.assert_allclose(ideal, actual, atol=1e-7, rtol=1e-7) - - if __name__ == '__main__': unittest.main() -else: - #plt.ion() - pass diff --git a/tftb/utils.py b/tftb/utils.py index 6c1a487..97eb5b2 100644 --- a/tftb/utils.py +++ b/tftb/utils.py @@ -74,12 +74,7 @@ def izak(x): def nextpow2(n): """ - #Compute the exponent of the next higher power of 2. - # returns the next *integer* exponent (as a float) and is lower-inclusive. - # ie,: - # > nextpow2(2) = 1.0 - # > nextpow2(2.0) = 1.0 - # > nextpow2(2.1) = 2.0 + Compute the integer exponent of the next higher power of 2. :param n: Number whose next higest power of 2 needs to be computed. :type n: int, np.ndarray