Repository: Theano/Theano

Issue:
when computing grad with many taps `0` is removed twice
To reproduce the problem

``` python
# imports
import pymc3 as pm
import theano
import theano.tensor as tt
import numpy as np

#data
x = theano.shared(np.random.normal(size=(1000,)), name='x')
a = theano.shared(np.ones(3), name='a')
b = theano.shared(np.ones(3), name='b')
evec=tt.constant(np.zeros(3), name='evec')
g = theano.shared(1, name='g')


def lags(x, k=1, axis=-1):
    m = x.shape[0]  # total length
    slices = []
    for i in range(k):
        slices.append(x[k - i - 1:m - i])
    return tt.stack(slices, axis=axis)

def gete(x, alpha, beta, gamma, evec, p=3, d=1, q=3):
    L = lags(x, k=p+1)
    dL = tt.extra_ops.diff(L, d, 0)
    y, X = dL[:,0], dL[:,1:]
    etaps = list(-i for i in range(q))
    def e_hat(y, X, *es):
        e = tt.stack(es[:-2])
        return y - tt.dot(X, es[-2]) - tt.dot(e, es[-1])

    e, _ = theano.scan(fn=e_hat, sequences=[y, X],
                outputs_info=dict(initial=evec, taps=etaps),
                non_sequences=[alpha, beta], strict=True)
    return pm.Normal.dist(0, tt.exp(gamma)).logp(e)

e = gete(x, a,b,g, evec)
dedw = theano.grad(e.mean(), [a,b,g])
# ValueError: list.remove(x): x not in list
# in theano/scan_module/scan_op.py#783
# because the following happened
# self.tap_array <class 'list'>: [[0, 0, 1, 2], [-1], [-1]]
```

The first zero was appended here ( to `mitmot_inp_taps` )
`theano/scan_module/scan_op.py#2329` in `Scan.grad`
Second was appended here ( to `mitmot_inp_taps` )
in loop `for jdx in xrange(len(self.tap_array[idx_tap])):`
where `tap_array <class 'list'>: [[0, -1, -2]]`, `idx_tap <int>: 0`
`theano/scan_module/scan_op.py#2343` in `Scan.grad`
after loop we can see
`mitmot_inp_taps <class 'list'>: [[0, 0, 1, 2]]`
then more interesting

``` python
# `theano/scan_module/scan_op.py#2479` in `Scan.grad`
new_tap_array = mitmot_inp_taps + [[-1] for k in
                                           xrange(n_sitsot_outs)]
info = OrderedDict()
...
info['tap_array'] = new_tap_array
...
local_op = Scan(inner_gfn_ins, inner_gfn_outs, info)
# it is obvious what's gonna happen soon
```

Is it a bug or i'm doing wrong?



Discussion:
Error was caused by
`etaps = list(-i for i in range(q))`
It should be
`etaps = list(-i for i in range(q+1)[1:])`
Place a debug message for that case please, it was not easy to guess that. Only morning helped.

thanks for the report. I don't know when we can work on this, but as an issue it won't get forgotten. If by change you wan to work on it, it would make this be merged in Theano faster.

Not sure why this was closed.

By error. Thanks.

I think the fix would be that if the same tap appear more then once, we should raise an error. The code don't support it. Also, we  should raise an error is the tap 0 is in the list of tap. I think only negative taps are supported by the code.


Resolving patch:
diff --git a/theano/scan_module/scan.py b/theano/scan_module/scan.py
--- a/theano/scan_module/scan.py
+++ b/theano/scan_module/scan.py
@@ -449,6 +449,16 @@ def wrap_into_list(x):
                              getattr(outs_info[i]['initial'], 'name', 'None'),
                              i)
                 outs_info[i]['taps'] = [-1]
+            elif outs_info[i].get('taps', None) is not None:
+                # Check that taps are valid (< 0 and all dfferent)
+                taps = outs_info[i]['taps']
+                if len(taps) > len(set(taps)):
+                    raise ValueError(('All the taps must be different in '
+                                      ' `outputs_info`'), outs_info[i])
+                for t in taps:
+                    if t >= 0:
+                        raise ValueError(('All the tap values must be '
+                                          'smaller than 0.'), outs_info[i])
         else:
             # if a None is provided as the output info we replace it
             # with an empty OrdereDict() to simplify handling


Test patch:
diff --git a/theano/scan_module/tests/test_scan.py b/theano/scan_module/tests/test_scan.py
--- a/theano/scan_module/tests/test_scan.py
+++ b/theano/scan_module/tests/test_scan.py
@@ -5454,3 +5454,15 @@ def test_constant_folding_n_steps():
         theano.function([], res)()
     finally:
         theano.config.on_opt_error = on_opt_error
+
+
+def test_outputs_taps_check():
+    """Checks that errors are raised with bad output_info taps."""
+    x = tensor.fvector('x')
+    y = tensor.fvector('y')
+    f = lambda x, y: [x]
+    outputs_info = {'initial': y, 'taps': [0]}
+    assert_raises(ValueError, theano.scan, f, x, outputs_info)
+    outputs_info = {'initial': y, 'taps': [-1, -1]}
+    assert_raises(ValueError, theano.scan, f, x, outputs_info)
+    print('done')