Repository: kymatio/kymatio

Issue:
Inconsistency in disabling second order
The order of the scattering transform is integer in 1d (`max_order=2`), boolean in 2d (`order2=True`), decided at forward runtime in 3d (`order_2=True`).
I would propose to unify this and make it integer, so that third-order scattering could, in principle, be implemented in the future.
I don't love the name `max_order`. The `max` prefix makes it sound like it's signal-adaptive, whereas it's a static choice.
I suggest `n_orders=2`.
If we want to have different outputs at runtime, we can always have kwarg `orders=[1,2]` in `forward`, but that's another story.


Discussion:
Either we do it now, either we postpone it to `0.2.0` along with a deprecation, and remove the deprecation in `0.3.0`. Thoughts?
It should be noted that 1D used to be `order2=True`, as well. I changed it for the reasons you describe (more general, easier to add zeroth order, third order, and so on).

Not sure about `n_orders`. Note that current `max_order=2` corresponds to `n_orders=3` since we have orders zero, one, and two. On the other hand, I'm not fixated on `max_order` either.


I copied the order2 arg from 1d to 2d when adding this feature feel free to sync them
> Not sure about n_orders. Note that current max_order=2 corresponds to n_orders=3 since we have orders zero, one, and two.

Oh my, that's a very good point.

Come to think of it, I like `max_order` more than `n_orders`. Because the `orders` can be changed at runtime between `0` and `max_order-1`. Shall I make a PR?
> Come to think of it, I like `max_order` more than `n_orders`.

Ok great!

> Because the `orders` can be changed at runtime between `0` and `max_order-1`.

Am I missing something here or do you mean `orders` can contain values between `0` and `max_order`?

> Shall I make a PR?

Yes please!
> Am I missing something here or do you mean orders can contain values between 0 and max_order?

No, you're right. Anywhere between `0` and `max_order`. And it defaults to `range(0, 1+max_order)`. 
I got bitten by EWD 831 again
wait your telling me I can do Scattering2d(max_order=0)?  lol
@eugenium I mean, it's a pretty contrived way to compute a Gaussian blur, but who's to say that there will never be a use case for it? :p
> wait your telling me I can do Scattering2d(max_order=0)? lol

Why not? I think it works in `Scattering1D` already. Go ahead, try it for yourself!
Sorry I'm late to this discussion.
I would have weighed in for e.g. `orders=(0, 1, 2)` at construction.

The proposed solution is OK, though, and since there is an almost finished PR for it, we should move that direction.
@eickenberg the kwarg `orders` can be added later.
here we do not have one because we only officially support `orders=range(0, 1+max_order)`
but this kwarg is useful for:
* skipping the zeroth order (in audio for example)
* skipping the second order without having to construct a whole new operator

`max_order` would be stored as `_max_order` (pseudo-immutable) in the object
but
`orders` would be stored as `orders` because it is mutable (as any subset of `range(0, 1+max_order)`)

would you be OK with this agenda?
>  this kwarg

meaning `orders=` ?

> would you be OK with this agenda?

agenda == 1. add `max_order` now 2. add `orders` some time in the future?
then yes
yes, `this kwarg` is `orders` :) 
shall we rebase and merge #212 then?
Yes

On Thu, Nov 29, 2018, 2:08 PM Vincent Lostanlen <notifications@github.com
wrote:

> yes, this kwarg is orders :)
> shall we rebase and merge #212
> <https://github.com/kymatio/kymatio/pull/212> then?
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/kymatio/kymatio/issues/196#issuecomment-443012002>,
> or mute the thread
> <https://github.com/notifications/unsubscribe-auth/ABPwC2lcJZxeJ9X-qSnqpnqGIDOUjAINks5u0FrsgaJpZM4YxqNa>
> .
>

So is this done?
@janden no, #212 was only for 2D. For 1D it's just a rename. For 3D it's a little bit more involved because at the moment the number of orders is a kwarg of `forward`. It should be made part of the constructor 
Ok. What's the rename in 1D?
I stand corrected. No rename needed: it's already named `max_order`, as it should. *À la bonne heure!* ;)
Ok. Agreed about 3D. Will look into that tomorrow. In general, I think we should avoid arguments to `forward` and have these be attributes set in the object (like we did in `Scattering1D`).
yes, i remember you writing that. i agree. but 3d has a lot of runtime forward kwargs, so it's overdue for a refactoring of the API

Will make a separate issue.

Resolving patch:
diff --git a/examples/3d/scattering3d_qm7.py b/examples/3d/scattering3d_qm7.py
--- a/examples/3d/scattering3d_qm7.py
+++ b/examples/3d/scattering3d_qm7.py
@@ -207,20 +207,20 @@ def compute_qm7_solid_harmonic_scattering_coefficients(
                 grid, pos_batch, full_batch, sigma, cuda=cuda)
         full_order_0 = compute_integrals(full_density_batch, integral_powers)
         full_scattering = scattering(
-                full_density_batch, order_2=True, method='integral',
+                full_density_batch, max_order=2, method='integral',
                 integral_powers=integral_powers)
 
         val_density_batch = generate_weighted_sum_of_gaussians(
                 grid, pos_batch, val_batch, sigma, cuda=cuda)
         val_order_0 = compute_integrals(val_density_batch, integral_powers)
         val_scattering= scattering(
-                val_density_batch, order_2=True, method='integral',
+                val_density_batch, max_order=2, method='integral',
                 integral_powers=integral_powers)
 
         core_density_batch = full_density_batch - val_density_batch
         core_order_0 = compute_integrals(core_density_batch, integral_powers)
         core_scattering = scattering(
-                core_density_batch, order_2=True, method='integral',
+                core_density_batch, max_order=2, method='integral',
                 integral_powers=integral_powers)
 
 
diff --git a/kymatio/scattering3d/scattering3d.py b/kymatio/scattering3d/scattering3d.py
--- a/kymatio/scattering3d/scattering3d.py
+++ b/kymatio/scattering3d/scattering3d.py
@@ -264,7 +264,7 @@ def _check_input(self, input_array):
         if (input_array.dim() != 4):
             raise (RuntimeError('Input tensor must be 4D'))
 
-    def forward(self, input_array, order_2=True, rotation_covariant=True,
+    def forward(self, input_array, max_order=2, rotation_covariant=True,
                 method='standard', points=None, integral_powers=(.5, 1., 2.)):
         """
         The forward pass of 3D solid harmonic scattering
@@ -273,9 +273,9 @@ def forward(self, input_array, order_2=True, rotation_covariant=True,
         ----------
         input_array: torch tensor 
             input of size (batchsize, M, N, O)
-        order_2: bool, optional
-            if set to False|True it also excludes|includes second order
-            scattering coefficients (default: True).
+        max_order: int, optional
+            The maximum order of scattering coefficients to compute. Must be
+            either 1 or 2. Defaults to 2.
         rotation_covariant: bool, optional
             if set to True the first order moduli take the form::
 
@@ -301,11 +301,11 @@ def forward(self, input_array, order_2=True, rotation_covariant=True,
         Returns
         -------
         output: tuple | torch tensor
-            if order_2 is false it returns a torch tensor with the
+            if max_order is 1 it returns a torch tensor with the
             first order scattering coefficients
-            if order_2 is true it returns a tuple with two elements,
-            the first and second order scattering coefficients
-
+            if max_order is 2 it returns a torch tensor with the
+            first and second order scattering coefficients,
+            concatenated along the feature axis
         """
         self._check_input(input_array)
         if rotation_covariant:
@@ -328,7 +328,7 @@ def forward(self, input_array, order_2=True, rotation_covariant=True,
                 conv_modulus = convolution_and_modulus(_input, l, j_1)
                 s_order_1_l.append(compute_scattering_coefs(
                     conv_modulus, method, method_args, j_1))
-                if not order_2:
+                if max_order == 1:
                     continue
                 for j_2 in range(j_1+1, self.J+1):
                     conv_modulus_2 = convolution_and_modulus(
@@ -336,10 +336,10 @@ def forward(self, input_array, order_2=True, rotation_covariant=True,
                     s_order_2_l.append(compute_scattering_coefs(
                         conv_modulus_2, method, method_args, j_2))
             s_order_1.append(torch.cat(s_order_1_l, -1))
-            if order_2:
+            if max_order == 2:
                 s_order_2.append(torch.cat(s_order_2_l, -1))
 
-        if order_2:
+        if max_order == 2:
             return torch.cat(
                 [torch.stack(s_order_1, dim=-1),
                 torch.stack(s_order_2, dim=-1)], -2)


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
@@ -76,7 +76,7 @@ def test_against_standard_computations():
             x = x.cuda()
         order_0 = compute_integrals(x, integral_powers)
         orders_1_and_2 = scattering(
-            x, order_2=True, method='integral',
+            x, max_order=2, method='integral',
             integral_powers=integral_powers)
 
         # WARNING: These are hard-coded values for the setting J = 2.
@@ -128,7 +128,7 @@ def test_solid_harmonic_scattering():
         np.fft.ifftshift(np.mgrid[-M//2:-M//2+M, -N//2:-N//2+N, -O//2:-O//2+O].astype('float32'), axes=(1,2,3)))
     x = generate_weighted_sum_of_gaussians(grid, centers, weights, sigma_gaussian)
     scattering = Scattering3D(J=J, shape=(M, N, O), L=L, sigma_0=sigma_0_wavelet)
-    s = scattering(x, order_2=False, method='integral', integral_powers=[1])
+    s = scattering(x, max_order=1, method='integral', integral_powers=[1])
 
     for j in range(J+1):
         sigma_wavelet = sigma_0_wavelet*2**j