Repository: kymatio/kymatio

Issue:
BUG missing one second-order wavelet for low-T Sc1D and JTFS
MWE 

```python
import numpy as np
from kymatio.numpy import Scattering1D

N = 1024
x = np.zeros(N)
S = Scattering1D(J=8, shape=(N,), T=2)
Sx = S(x)
```

Traceback:

```
IndexError                                Traceback (most recent call last)
Cell In [5], line 7
      4 kwargs = dict(J=8, shape=(N,))
      6 S = Scattering1D(**kwargs, T=2)
----> 7 S(x)

File ~/kymatio/kymatio/frontend/numpy_frontend.py:10, in ScatteringNumPy.__call__(self, x)
      7 def __call__(self, x):
      8     """This method is an alias for `scattering`."""
---> 10     return self.scattering(x)

File ~/kymatio/kymatio/scattering1d/frontend/base_frontend.py:116, in ScatteringBase1D.scattering(self, x)
    113 elif self.out_type == 'dict':
    114     S = dict()
--> 116 for path in S_gen:
    117     path['order'] = len(path['n'])
    118     if self.average == 'local':

File ~/kymatio/kymatio/scattering1d/core/scattering1d.py:97, in scattering1d(U_0, backend, filters, oversampling, average_local)
     94 sub2_adj = min(j2, log2_T) if average_local else j2
     95 k2 = max(sub2_adj - k1 - oversampling, 0)
---> 97 U_2_c = backend.cdgmm(U_1_hat, psi2[n2]['levels'][k1])
     98 U_2_hat = backend.subsample_fourier(U_2_c, 2**k2)
     99 # take the modulus

IndexError: list index out of range
```

Discussion:
cyrusvahidi: good catch, I was wondering why this issue was appearing

Patch:
diff --git a/kymatio/scattering1d/filter_bank.py b/kymatio/scattering1d/filter_bank.py
index 63e7dd5..b1d69ea 100644
--- a/kymatio/scattering1d/filter_bank.py
+++ b/kymatio/scattering1d/filter_bank.py
@@ -377,8 +377,8 @@ def scattering_filter_factory(N, J, Q, T, filterbank):
             j = get_max_dyadic_subsampling(xi, sigma, **filterbank_kwargs)
 
             # Resample to smaller resolutions if necessary (beyond 1st layer)
-            # The idiom min(previous_J, j, log2_T) implements "j1 < j2"
-            for level in range(1, min(previous_J, j, log2_T)):
+            # The idiom min(previous_J, j, 1+log2_T) implements "j1 < j2"
+            for level in range(1, min(previous_J, j, 1+log2_T)):
                 psi_level = psi_levels[0].reshape(2 ** level, -1).mean(axis=0)
                 psi_levels.append(psi_level)
             psi_f.append({'levels': psi_levels, 'xi': xi, 'sigma': sigma, 'j': j})


Test patch:
diff --git a/tests/scattering1d/test_numpy_scattering1d.py b/tests/scattering1d/test_numpy_scattering1d.py
index 8c84f7f..9908bff 100644
--- a/tests/scattering1d/test_numpy_scattering1d.py
+++ b/tests/scattering1d/test_numpy_scattering1d.py
@@ -114,6 +114,16 @@ class TestScattering1DNumpy:
         Sx = sc(x)
         assert Sx.shape[-1] == 1
 
+    def test_981(self, backend):
+        """
+        Tests against bug #981, in which some low values of T triggered an
+        `IndexError` in second-order backend.cdgmm
+        """
+        N = 1024
+        x = np.zeros(N)
+        sc = Scattering1D(J=8, shape=(N,), T=2)
+        Sx = sc(x)
+        assert Sx.shape[-1] == N/sc.T
 
 
 frontends = ['numpy', 'sklearn']