Repository: kymatio/kymatio

Issue:
`morlet_1d` and `gauss_1d` should be simpler
In a previous unmerged PR (#514), i suggested to remove `normalize`, `Pmax`, and `eps`  from 1D filterbank construction. I doubt anyone uses non-default values. So we could replace them by constants: `normalize="l1"`, `Pmax=5`, `eps=` machine precision. Furthermore, gauss1d and morlet1d could be unified into a single function.

@MuawizChaudhary has suggested to revive this PR. This will be backwards compatible.


Discussion:
+1 in support of this. would be nice to have for 0.3 release. 

Resolving patch:
diff --git a/kymatio/scattering1d/filter_bank.py b/kymatio/scattering1d/filter_bank.py
--- a/kymatio/scattering1d/filter_bank.py
+++ b/kymatio/scattering1d/filter_bank.py
@@ -71,49 +71,32 @@ def periodize_filter_fourier(h_f, nperiods=1):
     return v_f
 
 
-def morlet_1d(N, xi, sigma, normalize='l1', P_max=5, eps=1e-7):
+def morlet_1d(N, xi, sigma):
     """
-    Computes the Fourier transform of a Morlet filter.
-
+    Computes the Fourier transform of a Morlet or Gauss filter.
     A Morlet filter is the sum of a Gabor filter and a low-pass filter
     to ensure that the sum has exactly zero mean in the temporal domain.
     It is defined by the following formula in time:
-    psi(t) = g_{sigma}(t) (e^{i xi t} - beta)
-    where g_{sigma} is a Gaussian envelope, xi is a frequency and beta is
-    the cancelling parameter.
-
+    psi(t) = g_{sigma}(t) (e^{i xi t} - kappa)
+    where g_{sigma} is a Gaussian envelope, xi is a frequency and kappa is
+    a corrective term which ensures that psi has a null average.
+    If xi is None, the definition becomes: phi(t) = g_{sigma}(t)
     Parameters
     ----------
     N : int
         size of the temporal support
-    xi : float
-        central frequency (in [0, 1])
+    xi : float or None
+        center frequency in (0, 1]
     sigma : float
         bandwidth parameter
-    normalize : string, optional
-        normalization types for the filters. Defaults to 'l1'.
-        Supported normalizations are 'l1' and 'l2' (understood in time domain).
-    P_max: int, optional
-        integer controlling the maximal number of periods to use to ensure
-        the periodicity of the Fourier transform. (At most 2*P_max - 1 periods
-        are used, to ensure an equal distribution around 0.5). Defaults to 5
-        Should be >= 1
-    eps : float
-        required machine precision (to choose the adequate P)
-
     Returns
     -------
-    morlet_f : array_like
+    filter_f : array_like
         numpy array of size (N,) containing the Fourier transform of the Morlet
         filter at the frequencies given by np.fft.fftfreq(N).
     """
-    if type(P_max) != int:
-        raise ValueError('P_max should be an int, got {}'.format(type(P_max)))
-    if P_max < 1:
-        raise ValueError('P_max should be non-negative, got {}'.format(P_max))
-    # Find the adequate value of P
-    P = min(adaptive_choice_P(sigma, eps=eps), P_max)
-    assert P >= 1
+    # Find the adequate value of P<=5
+    P = min(adaptive_choice_P(sigma), 5)
     # Define the frequencies over [1-P, P[
     freqs = np.arange((1 - P) * N, P * N, dtype=float) / float(N)
     if P == 1:
@@ -122,98 +105,24 @@ def morlet_1d(N, xi, sigma, normalize='l1', P_max=5, eps=1e-7):
         freqs_low = np.fft.fftfreq(N)
     elif P > 1:
         freqs_low = freqs
-    # define the gabor at freq xi and the low-pass, both of width sigma
-    gabor_f = np.exp(-(freqs - xi)**2 / (2 * sigma**2))
     low_pass_f = np.exp(-(freqs_low**2) / (2 * sigma**2))
-    # discretize in signal <=> periodize in Fourier
-    gabor_f = periodize_filter_fourier(gabor_f, nperiods=2 * P - 1)
     low_pass_f = periodize_filter_fourier(low_pass_f, nperiods=2 * P - 1)
-    # find the summation factor to ensure that morlet_f[0] = 0.
-    kappa = gabor_f[0] / low_pass_f[0]
-    morlet_f = gabor_f - kappa * low_pass_f
-    # normalize the Morlet if necessary
-    morlet_f *= get_normalizing_factor(morlet_f, normalize=normalize)
-    return morlet_f
-
-
-def get_normalizing_factor(h_f, normalize='l1'):
-    """
-    Computes the desired normalization factor for a filter defined in Fourier.
-
-    Parameters
-    ----------
-    h_f : array_like
-        numpy vector containing the Fourier transform of a filter
-    normalized : string, optional
-        desired normalization type, either 'l1' or 'l2'. Defaults to 'l1'.
-
-    Returns
-    -------
-    norm_factor : float
-        such that h_f * norm_factor is the adequately normalized vector.
-    """
-    h_real = ifft(h_f)
-    if np.abs(h_real).sum() < 1e-7:
-        raise ValueError('Zero division error is very likely to occur, ' +
-                         'aborting computations now.')
-    if normalize == 'l1':
-        norm_factor = 1. / (np.abs(h_real).sum())
-    elif normalize == 'l2':
-        norm_factor = 1. / np.sqrt((np.abs(h_real)**2).sum())
+    if xi:
+        # define the gabor at freq xi and the low-pass, both of width sigma
+        gabor_f = np.exp(-(freqs - xi)**2 / (2 * sigma**2))
+        # discretize in signal <=> periodize in Fourier
+        gabor_f = periodize_filter_fourier(gabor_f, nperiods=2 * P - 1)
+        # find the summation factor to ensure that morlet_f[0] = 0.
+        kappa = gabor_f[0] / low_pass_f[0]
+        filter_f = gabor_f - kappa * low_pass_f # psi (band-pass) case
     else:
-        raise ValueError("Supported normalizations only include 'l1' and 'l2'")
-    return norm_factor
+        filter_f = low_pass_f # phi (low-pass) case
+    filter_f /= np.abs(ifft(filter_f)).sum()
+    return filter_f
 
 
-def gauss_1d(N, sigma, normalize='l1', P_max=5, eps=1e-7):
-    """
-    Computes the Fourier transform of a low pass gaussian window.
-
-    \\hat g_{\\sigma}(\\omega) = e^{-\\omega^2 / 2 \\sigma^2}
-
-    Parameters
-    ----------
-    N : int
-        size of the temporal support
-    sigma : float
-        bandwidth parameter
-    normalize : string, optional
-        normalization types for the filters. Defaults to 'l1'
-        Supported normalizations are 'l1' and 'l2' (understood in time domain).
-    P_max : int, optional
-        integer controlling the maximal number of periods to use to ensure
-        the periodicity of the Fourier transform. (At most 2*P_max - 1 periods
-        are used, to ensure an equal distribution around 0.5). Defaults to 5
-        Should be >= 1
-    eps : float, optional
-        required machine precision (to choose the adequate P)
-
-    Returns
-    -------
-    g_f : array_like
-        numpy array of size (N,) containing the Fourier transform of the
-        filter (with the frequencies in the np.fft.fftfreq convention).
-    """
-    # Find the adequate value of P
-    if type(P_max) != int:
-        raise ValueError('P_max should be an int, got {}'.format(type(P_max)))
-    if P_max < 1:
-        raise ValueError('P_max should be non-negative, got {}'.format(P_max))
-    P = min(adaptive_choice_P(sigma, eps=eps), P_max)
-    assert P >= 1
-    # switch cases
-    if P == 1:
-        freqs_low = np.fft.fftfreq(N)
-    elif P > 1:
-        freqs_low = np.arange((1 - P) * N, P * N, dtype=float) / float(N)
-    # define the low pass
-    g_f = np.exp(-freqs_low**2 / (2 * sigma**2))
-    # periodize it
-    g_f = periodize_filter_fourier(g_f, nperiods=2 * P - 1)
-    # normalize the signal
-    g_f *= get_normalizing_factor(g_f, normalize=normalize)
-    # return the Fourier transform
-    return g_f
+def gauss_1d(N, sigma):
+    return morlet_1d(N, xi=None, sigma=sigma)
 
 
 def compute_sigma_psi(xi, Q, r=math.sqrt(0.5)):
@@ -429,9 +338,7 @@ def compute_params_filterbank(sigma_min, Q, alpha, r_psi=math.sqrt(0.5)):
 
 
 def scattering_filter_factory(J_support, J_scattering, Q, T, r_psi=math.sqrt(0.5),
-                              normalize='l1',
-                              max_subsampling=None, sigma0=0.1, alpha=5.,
-                              P_max=5, eps=1e-7, **kwargs):
+                              max_subsampling=None, sigma0=0.1, alpha=5., **kwargs):
     """
     Builds in Fourier the Morlet filters used for the scattering transform.
 
@@ -462,10 +369,6 @@ def scattering_filter_factory(J_support, J_scattering, Q, T, r_psi=math.sqrt(0.5
         Should be >0 and <1. Controls the redundancy of the filters
         (the larger r_psi, the larger the overlap between adjacent wavelets).
         Defaults to sqrt(0.5).
-    normalize : string, optional
-        Normalization convention for the filters (in the
-        temporal domain). Supported values include 'l1' and 'l2'; a ValueError
-        is raised otherwise. Defaults to 'l1'.
     max_subsampling: int or None, optional
         maximal dyadic subsampling to compute, in order
         to save computation time if it is not required. Defaults to None, in
@@ -478,14 +381,6 @@ def scattering_filter_factory(J_support, J_scattering, Q, T, r_psi=math.sqrt(0.5
         tolerance factor for the aliasing after subsampling.
         The larger alpha, the more conservative the value of maximal
         subsampling is. Defaults to 5.
-    P_max : int, optional
-        maximal number of periods to use to make sure that the Fourier
-        transform of the filters is periodic. P_max = 5 is more than enough for
-        double precision. Defaults to 5. Should be >= 1
-    eps : float, optional
-        required machine precision for the periodization (single
-        floating point is enough for deep learning applications).
-        Defaults to 1e-7
 
     Returns
     -------
@@ -551,9 +446,7 @@ def scattering_filter_factory(J_support, J_scattering, Q, T, r_psi=math.sqrt(0.5
         N = 2**J_support
 
         psi_f = {}
-        psi_f[0] = morlet_1d(
-            N, xi2[n2], sigma2[n2], normalize=normalize, P_max=P_max,
-            eps=eps)
+        psi_f[0] = morlet_1d(N, xi2[n2], sigma2[n2])
         # compute the filter after subsampling at all other subsamplings
         # which might be received by the network, based on this first filter
         for subsampling in range(1, max_sub_psi2 + 1):
@@ -566,9 +459,7 @@ def scattering_filter_factory(J_support, J_scattering, Q, T, r_psi=math.sqrt(0.5
     # can only compute them with N=2**J_support
     for (n1, j1) in enumerate(j1s):
         N = 2**J_support
-        psi1_f.append({0: morlet_1d(
-            N, xi1[n1], sigma1[n1], normalize=normalize,
-            P_max=P_max, eps=eps)})
+        psi1_f.append({0: morlet_1d(N, xi1[n1], sigma1[n1])})
 
     # compute the low-pass filters phi
     # Determine the maximal subsampling for phi, which depends on the
@@ -583,7 +474,7 @@ def scattering_filter_factory(J_support, J_scattering, Q, T, r_psi=math.sqrt(0.5
         max_sub_phi = max_subsampling
 
     # compute the filters at all possible subsamplings
-    phi_f[0] = gauss_1d(N, sigma_low, P_max=P_max, eps=eps)
+    phi_f[0] = gauss_1d(N, sigma_low)
     for subsampling in range(1, max_sub_phi + 1):
         factor_subsampling = 2**subsampling
         # compute the low_pass filter
diff --git a/kymatio/scattering1d/frontend/base_frontend.py b/kymatio/scattering1d/frontend/base_frontend.py
--- a/kymatio/scattering1d/frontend/base_frontend.py
+++ b/kymatio/scattering1d/frontend/base_frontend.py
@@ -36,9 +36,6 @@ def build(self):
         self.r_psi = math.sqrt(0.5)
         self.sigma0 = 0.1
         self.alpha = 5.
-        self.P_max = 5
-        self.eps = 1e-7
-        self.normalize = 'l1'
 
         # check the number of filters per octave
         if self.Q < 1:
@@ -65,8 +62,7 @@ def build(self):
         self.log2_T = math.floor(math.log2(self.T))
 
         # Compute the minimum support to pad (ideally)
-        phi_f = gauss_1d(
-            self.N, self.sigma0/self.T, self.normalize, self.P_max, self.eps)
+        phi_f = gauss_1d(self.N, self.sigma0/self.T)
         min_to_pad = 3 * compute_temporal_support(
             phi_f.reshape(1, -1), criterion_amplitude=1e-3)
 
@@ -84,9 +80,8 @@ def build(self):
     def create_filters(self):
         # Create the filters
         self.phi_f, self.psi1_f, self.psi2_f = scattering_filter_factory(
-            self.J_pad, self.J, self.Q, self.T, normalize=self.normalize,
-            r_psi=self.r_psi, sigma0=self.sigma0, alpha=self.alpha,
-            P_max=self.P_max, eps=self.eps)
+            self.J_pad, self.J, self.Q, self.T,
+            r_psi=self.r_psi, sigma0=self.sigma0, alpha=self.alpha)
 
     def meta(self):
         """Get meta information on the transform


Test patch:
diff --git a/tests/scattering1d/test_correctness.py b/tests/scattering1d/test_correctness.py
--- a/tests/scattering1d/test_correctness.py
+++ b/tests/scattering1d/test_correctness.py
@@ -3,10 +3,6 @@
 from kymatio.numpy import Scattering1D
 
 
-# set True to execute all test functions without pytest
-run_without_pytest = 0
-
-
 def test_T():
     """Test that `T` controls degree of invariance as intended."""
     # configure scattering & signal
diff --git a/tests/scattering1d/test_filters_scattering1d.py b/tests/scattering1d/test_filters_scattering1d.py
--- a/tests/scattering1d/test_filters_scattering1d.py
+++ b/tests/scattering1d/test_filters_scattering1d.py
@@ -1,7 +1,8 @@
 """
 Testing all functions in filters_bank
 """
-from kymatio.scattering1d.filter_bank import (adaptive_choice_P, periodize_filter_fourier, get_normalizing_factor,
+
+from kymatio.scattering1d.filter_bank import (adaptive_choice_P, periodize_filter_fourier,
     compute_sigma_psi, compute_temporal_support, compute_xi_max, morlet_1d,
     get_max_dyadic_subsampling, gauss_1d)
 import numpy as np
@@ -48,34 +49,6 @@ def test_periodize_filter_fourier(random_state=42):
             assert np.max(np.abs(x_per - x[::per])) < 1e-7
 
 
-def test_normalizing_factor(random_state=42):
-    """
-    Tests whether the computation of the normalizing factor does the correct
-    job (i.e. actually normalizes the signal in l1 or l2)
-    """
-    rng = np.random.RandomState(random_state)
-    size_signal = [2**j for j in range(5, 13)]
-    norm_type = ['l1', 'l2']
-    for N in size_signal:
-        x = rng.randn(N) + 1j * rng.randn(N)
-        x_f = np.fft.fft(x)
-        for norm in norm_type:
-            kappa = get_normalizing_factor(x_f, norm)
-            x_norm = kappa * x
-            if norm == 'l1':
-                assert np.isclose(np.sum(np.abs(x_norm)) - 1, 0.)
-            elif norm == 'l2':
-                assert np.isclose(np.sqrt(np.sum(np.abs(x_norm)**2)) - 1., 0.)
-
-    with pytest.raises(ValueError) as ve:
-        get_normalizing_factor(np.zeros(4))
-    assert "Zero division error is very likely" in ve.value.args[0]
-
-    with pytest.raises(ValueError) as ve:
-        get_normalizing_factor(np.ones(4), normalize='l0')
-    assert "normalizations only include" in ve.value.args[0]
-
-
 def test_morlet_1d():
     """
     Tests for Morlet wavelets:
@@ -86,39 +59,25 @@ def test_morlet_1d():
     """
     size_signal = [2**13]
     Q_range = np.arange(1, 20, dtype=int)
-    P_range = [1, 5]
     for N in size_signal:
         for Q in Q_range:
             xi_max = compute_xi_max(Q)
             xi_range = xi_max / np.power(2, np.arange(7))
             for xi in xi_range:
-                for P in P_range:
-                    sigma = compute_sigma_psi(xi, Q)
-                    # get the morlet for these parameters
-                    psi_f = morlet_1d(N, xi, sigma, normalize='l2', P_max=P)
-                    # make sure that it has zero mean
-                    assert np.isclose(psi_f[0], 0.)
-                    # make sure that it has a fast decay in time
-                    psi = np.fft.ifft(psi_f)
-                    psi_abs = np.abs(psi)
-                    assert np.min(psi_abs) / np.max(psi_abs) < 1e-3
-                    # Check that the maximal frequency is relatively close to xi,
-                    # up to 1 percent
-                    k_max = np.argmax(np.abs(psi_f))
-                    xi_emp = float(k_max) / float(N)
-                    assert np.abs(xi_emp - xi) / xi < 1e-2
-
-    Q = 1
-    xi = compute_xi_max(Q)
-    sigma = compute_sigma_psi(xi, Q)
-
-    with pytest.raises(ValueError) as ve:
-        morlet_1d(size_signal[0], xi, sigma, P_max=5.1)
-    assert "should be an int" in ve.value.args[0]
-
-    with pytest.raises(ValueError) as ve:
-        morlet_1d(size_signal[0], xi, sigma, P_max=-5)
-    assert "should be non-negative" in ve.value.args[0]
+                sigma = compute_sigma_psi(xi, Q)
+                # get the morlet for these parameters
+                psi_f = morlet_1d(N, xi, sigma)
+                # make sure that it has zero mean
+                assert np.isclose(psi_f[0], 0.)
+                # make sure that it has a fast decay in time
+                psi = np.fft.ifft(psi_f)
+                psi_abs = np.abs(psi)
+                assert np.min(psi_abs) / np.max(psi_abs) < 1e-3
+                # Check that the maximal frequency is relatively close to xi,
+                # up to 1 percent
+                k_max = np.argmax(np.abs(psi_f))
+                xi_emp = float(k_max) / float(N)
+                assert np.abs(xi_emp - xi) / xi < 1e-2
 
 
 def test_gauss_1d():
@@ -129,31 +88,17 @@ def test_gauss_1d():
     """
     N = 2**13
     J = 7
-    P_range = [1, 5]
     sigma0 = 0.1
     tol = 1e-7
     for j in range(1, J + 1):
-        for P in P_range:
-            sigma_low = sigma0 / math.pow(2, j)
-            g_f = gauss_1d(N, sigma_low, P_max=P)
-            # check the symmetry of g_f
-            assert np.max(np.abs(g_f[1:N // 2] - g_f[N // 2 + 1:][::-1])) < tol
-            # make sure that it has a fast decay in time
-            phi = np.fft.ifft(g_f)
-            assert np.min(phi) > - tol
-            assert np.min(np.abs(phi)) / np.max(np.abs(phi)) < 1e-4
-
-    Q = 1
-    xi = compute_xi_max(Q)
-    sigma = compute_sigma_psi(xi, Q)
-
-    with pytest.raises(ValueError) as ve:
-        gauss_1d(N, xi, sigma, P_max=5.1)
-    assert "should be an int" in ve.value.args[0]
-
-    with pytest.raises(ValueError) as ve:
-        gauss_1d(N, xi, sigma, P_max=-5)
-    assert "should be non-negative" in ve.value.args[0]
+        sigma_low = sigma0 / math.pow(2, j)
+        g_f = gauss_1d(N, sigma_low)
+        # check the symmetry of g_f
+        assert np.max(np.abs(g_f[1:N // 2] - g_f[N // 2 + 1:][::-1])) < tol
+        # make sure that it has a fast decay in time
+        phi = np.fft.ifft(g_f)
+        assert np.min(phi) > - tol
+        assert np.min(np.abs(phi)) / np.max(np.abs(phi)) < 1e-4
 
 
 def test_compute_xi_max():