Repository: kymatio/kymatio

Issue:
`precompute_size_scattering` should always return a tuple (`detail=False`)
Came up in conversation with @MuawizChaudhary 

The `detail` kwarg in `precompute_size_scattering` isn't really justified:
* it is `True` by default
* it creates type instability (`int` or `tuple`)
* getting the `False` version is trivial: it suffices to call `sum` on the output
* our object-oriented method `self.output_size`, has `detail=False` by default. I suggest removing that kwarg and simply summing across orders after calling `precompute_size_scattering`

Hence i propose to remove it. The only occasion where we need `self.output_size()` in the codebase is in the Keras frontend. 
Perhaps we can set up a helpful error message if people ever call the non-default: `self.output_size(detail=True)`, telling them to call `self.meta()` instead?


Discussion:
I agree alot with having a tuple output, and also agree with the idea of calling sum on the tuple output if need be. We need to be careful about erroneously counting order 2 scattering coefficients when there arent any; a test can provide a check of correctness. 

I don't believe we should add a error message. 


So should we keep accommodating both `self.output_size(detail=True)` and `self.output_size(detail=False)` ?
`self.output_size(detail=False)`.
That's a breaking change, but OK. It will cause this in user code:

> self.output_size(detail=True)
> TypeError: output_size() got an unexpected keyword argument 'detail'

@MuawizChaudhary shouldn't we produce a more helpful error message?
If you think its a good idea, we can add a error message stating that we removed `detail=True`. 

I wouldn't expect many users to have used `self.output_size`. however. Personally, I think we should have a variable in `Scattering1D` that holds the size of the scattering, and this variable should be named `num_coefficents` or something like that. 

That is, I dont think we should even have this be a user exposed function. 
> If you think its a good idea, we can add a error message stating that we removed detail=True.

Then, i suggest we go with the breaking change, release v0.3, and if anyone complains about `output_size() got an unexpected keyword argument 'detail'`, i'll write a helpful error message for v0.3.1. Deal?


> Personally, I think we should have a variable in Scattering1D that holds the size of the scattering, and this variable should be named num_coefficents or something like that. That is, I dont think we should even have this be a user exposed function.

I disagree. If it's a variable in the state of the program, then potentially people can modify/delete it, which will lead to antipatterns and strange hacks. Whereas making it the return value of a method (`self.output_size()`) prevents that problem. I see this method as a "getter" on a private field.
Sounds like a plan : )
> Then, i suggest we go with the breaking change, release v0.3, and if anyone complains about `output_size() got an unexpected keyword argument 'detail'`, i'll write a helpful error message for v0.3.1. Deal?

Sounds good to me.



> I disagree. If it's a variable in the state of the program, then potentially people can modify/delete it, which will lead to antipatterns and strange hacks. Whereas making it the return value of a method (`self.output_size()`) prevents that problem. I see this method as a "getter" on a private field.

Yes let's not mix styles here. We already have `meta` as a function, so the same should be true of `output_size`. I see @MuawizChaudhary's point about the need for the function (technically all the information is already in `meta`), but it's still useful to have as a shorthand for `len(scattering.meta)` or something similar.
So just to make sure I've got this right, `precompute_size_scattering` will fix `detail=True` and `output_size` will fix `detail=False`?
> So just to make sure I've got this right, precompute_size_scattering will fix detail=True and output_size will fix detail=False?

Yes, that's what we just said ... but that would remove useful functionality, namely the `out_size(detail=True)` non-default. So i'm no longer in favor of it (at first, i believed that `out_size()` was currently `detail`-ed by default, but the opposite is true)

Looking back at it, i don't think there's much to be done here, except maybe removing the confusing `detail` keyword argument from `precompute_size_scattering`, and doing the summation inside `out_size`. This will be fully backwards compatible and will reduce the number of conditional branches in unit tests.
All right. Works for me.

Resolving patch:
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
@@ -113,8 +113,12 @@ def output_size(self, detail=False):
         size : int or tuple
             See the documentation for `precompute_size_scattering()`.
         """
-        return precompute_size_scattering(self.J, self.Q, self.T,
-            self.max_order, self.r_psi, self.sigma0, self.alpha, detail=detail)
+        size = precompute_size_scattering(self.J, self.Q, self.T,
+            self.max_order, self.r_psi, self.sigma0, self.alpha)
+        if not detail:
+            size = sum(size)
+        return size
+
 
     def _check_runtime_args(self):
         if not self.out_type in ('array', 'dict', 'list'):
diff --git a/kymatio/scattering1d/utils.py b/kymatio/scattering1d/utils.py
--- a/kymatio/scattering1d/utils.py
+++ b/kymatio/scattering1d/utils.py
@@ -70,8 +70,7 @@ def compute_padding(J_pad, N):
     return pad_left, pad_right
 
 
-def precompute_size_scattering(
-        J, Q, T, max_order, r_psi, sigma0, alpha, detail=False):
+def precompute_size_scattering(J, Q, T, max_order, r_psi, sigma0, alpha):
     """Get size of the scattering transform
 
     The number of scattering coefficients depends on the filter
@@ -103,38 +102,27 @@ def precompute_size_scattering(
         tolerance factor for the aliasing after subsampling.
         The larger alpha, the more conservative the value of maximal
         subsampling is.
-    detail : boolean, optional
-        Specifies whether to provide a detailed size (number of coefficient
-        per order) or an aggregate size (total number of coefficients).
 
     Returns
     -------
-    size : int or tuple
-        If `detail` is `False`, returns the number of coefficients as an
-        integer. If `True`, returns a tuple of size `max_order` containing
-        the number of coefficients in each order.
+    size : tuple
+        A tuple of size `1+max_order` containing the number of coefficients in
+        orders zero up to `max_order`, both included.
     """
     sigma_min = sigma0 / math.pow(2, J)
     xi1s, sigma1s, j1s = compute_params_filterbank(sigma_min, Q, alpha, r_psi)
     xi2s, sigma2s, j2s = compute_params_filterbank(sigma_min, 1, alpha, r_psi)
 
-    size_order0 = 1
-    size_order1 = len(xi1s)
+    sizes = [1, len(xi1s)]
     size_order2 = 0
     for n1 in range(len(xi1s)):
         for n2 in range(len(xi2s)):
             if j2s[n2] > j1s[n1]:
                 size_order2 += 1
-    if detail:
-        if max_order == 2:
-            return size_order0, size_order1, size_order2
-        else:
-            return size_order0, size_order1
-    else:
-        if max_order == 2:
-            return size_order0 + size_order1 + size_order2
-        else:
-            return size_order0 + size_order1
+
+    if max_order == 2:
+        sizes.append(size_order2)
+    return sizes
 
 
 def compute_meta_scattering(J, Q, T, max_order, r_psi, sigma0, alpha):


Test patch: