Repository: kymatio/kymatio

Issue:
`Scattering3D` crashes for `J != 2`
This was introduced by concatenation along the wrong axis in 36bfb11f29c0e7a3cddfc1eedc23ba84e9c17ea8. Instead of concatenating along axis 1 (that is, the spatial dimension), the concatenation should (of course) be along axis 2 (that is, the filter index/scale dimension).

It was not caught by the test suite since all tests have `J = 2`, which has the special property that the number of first-order scales (3) is equal to the number of second-order scales (2+1). We should modify the test suite so that this doesn't happen.


Discussion:
:confounded: 
could you open a PR with a test added to the 3d that fails?
Unfortunately, this cannot be easily resolved at the moment without reverting #210 or severely butchering the `test_against_standard_computations` test in the 3D test suite. The issue is the (incompatible) storage of the output array stored as part of the test and the array output by `Scattering3D`. The former is stored in the layout `(order*integral_power*scale*ell)` (as a flat array), while the latter is stored as `(integral_power, order*scale, ell)` (as a 3D array). Ideally, we'd want to store the output as `(order, integral_power, scale, ell)` which can then be easily flattened, but that's not possible since each order has a different number of scales (unless `J=2`, of course).

One solution is to hardcode the number of order-one scales in the unit test so that those can be extracted from the stored reference and the array can be permuted to align with the output of `Scattering3D`. Another is to go back to tuples. Another is to permute the output of `Scattering3D` to instead read `(order*scale, integral_power, ell)`, but that would require quite a bit of work.
> could you open a PR with a test added to the 3d that fails?

Yes.

Resolving patch:
diff --git a/kymatio/scattering3d/scattering3d.py b/kymatio/scattering3d/scattering3d.py
--- a/kymatio/scattering3d/scattering3d.py
+++ b/kymatio/scattering3d/scattering3d.py
@@ -342,7 +342,7 @@ def forward(self, input_array, order_2=True, rotation_covariant=True,
         if order_2:
             return torch.cat(
                 [torch.stack(s_order_1, dim=-1),
-                torch.stack(s_order_2, dim=-1)], 1)
+                torch.stack(s_order_2, dim=-1)], -2)
         else:
             return torch.stack(s_order_1, dim=-1)
 


Test patch:
diff --git a/kymatio/scattering3d/tests/test_scattering3d.py b/kymatio/scattering3d/tests/test_scattering3d.py
--- a/kymatio/scattering3d/tests/test_scattering3d.py
+++ b/kymatio/scattering3d/tests/test_scattering3d.py
@@ -78,6 +78,19 @@ def test_against_standard_computations():
             x, order_2=True, method='integral',
             integral_powers=integral_powers)
 
+        # WARNING: These are hard-coded values for the setting J = 2.
+        n_order_1 = 3
+        n_order_2 = 3
+
+        # Extract orders and make order axis the slowest in accordance with
+        # the stored reference scattering transform.
+        order_1 = orders_1_and_2[:,:,0:n_order_1,:]
+        order_2 = orders_1_and_2[:,:,n_order_1:n_order_1+n_order_2,:]
+
+        order_1 = order_1.reshape((batch_size, -1))
+        order_2 = order_2.reshape((batch_size, -1))
+
+        orders_1_and_2 = torch.cat((order_1, order_2), 1)
 
         order_0 = order_0.cpu().numpy().reshape((batch_size, -1))
         start = 0
@@ -122,3 +135,20 @@ def test_solid_harmonic_scattering():
         for l in range(1, L+1):
             err = torch.abs(s[0, 0, j, l] - k ** l).sum()/(1e-6+s[0, 0, j, l].abs().sum())
             assert err<1e-4
+
+def test_larger_scales():
+    if backend.NAME == "skcuda":
+        warnings.warn(("The skcuda backend is not yet implemented for 3D "
+            "scattering, but that's ok (for now)."), RuntimeWarning,
+            stacklevel=2)
+        return
+
+    shape = (32, 32, 32)
+    L = 3
+    sigma_0 = 1
+
+    x = torch.randn((1,) + shape)
+
+    for J in range(3, 4+1):
+        scattering = Scattering3D(J=J, shape=shape, L=L, sigma_0=sigma_0)
+        Sx = scattering(x, method='integral')