Repository: Theano/Theano

Issue:
Scan.connection_pattern IndexError with multiple taps and output variables
From https://groups.google.com/d/topic/theano-users/TcA2oaNtbs0/discussion

``` python
import numpy as np
import theano
import theano.tensor as T

def fn(a_m2, a_m1, b_m2, b_m1):
    return a_m1, b_m1

a0 = theano.shared(np.arange(2))
b0 = theano.shared(np.arange(2))

(a, b), _ = theano.scan(fn,
                        outputs_info=[{'initial': a0, 'taps': [-2, -1]},
                                      {'initial': b0, 'taps': [-2, -1]}],
                        n_steps=2)
T.grad(a[-1], a0)
```



Discussion:
As I said on the mailing list, I reproduced the bug and I know what the problem it but I couldn't implement it today. I will do it tomorrow and submit a PR.


Resolving patch:
diff --git a/theano/scan_module/scan_op.py b/theano/scan_module/scan_op.py
--- a/theano/scan_module/scan_op.py
+++ b/theano/scan_module/scan_op.py
@@ -1400,15 +1400,33 @@ def _get_inner_inps(iidx):
                             tmp = ils
                         if any([x is not None for x in tmp]):
                             connection_pattern[iidx + 1][oidx] = True
+
         # Applying Floyd-Warshall to find all paths connecting inputs to
         # outputs. Note that if `x` is an input to `y_t` and `y_tm1` is an
         # input to `z_t` then `x` is an input to `z_t`.
 
         n_outs = len(node.outputs)
+        outer_iidx_from_inner_iidx = self.get_outer_iidx_from_inner_iidx_seq()
+
         for steps in xrange(n_outs):
             for iidx in xrange(n_outs):
                 for jidx in xrange(n_outs):
-                    j_inp_idx = self.get_input_pos(jidx) + 1
+
+                    # Get the idx of the first inner input corresponding to
+                    # that inner output
+                    j_inp_idx = self.get_input_pos(jidx)
+
+                    if j_inp_idx == -1:
+                        # No corresponding inner input : default to what scan
+                        # was doing in the previous version in those cases
+                        # which *seems* to be a hack designed to avoid passing
+                        # the condition below but it's not certain.
+                        j_inp_idx = 0
+                    else:
+                        # Get the idx of the outer input corresponding to that
+                        # inner input
+                        j_inp_idx = outer_iidx_from_inner_iidx[j_inp_idx]
+
                     if connection_pattern[j_inp_idx][iidx] == True:
                         for k in xrange(len(connection_pattern)):
                             if connection_pattern[k][jidx]:
@@ -1417,6 +1435,42 @@ def _get_inner_inps(iidx):
         node.tag.connection_pattern = connection_pattern
         return connection_pattern
 
+    def get_outer_iidx_from_inner_iidx_seq(self):
+        """ Return a sequence where the value of at the i-th position is the
+        index of the outer input corresponding to the i-th inner input
+        """
+
+        output = []
+        outer_inp_idx = 1 # First outer input is timestep index, skip it
+
+        # Handle sequences inputs
+        for i in range(self.info['n_seqs']):
+            output.append(outer_inp_idx)
+            outer_inp_idx += 1
+
+        # Handle mitmots, mitsots and sitsots inputs
+        for input_taps in self.info['tap_array']:
+            for tap in input_taps:
+                output.append(outer_inp_idx)
+            outer_inp_idx += 1
+
+        # Handle shared inputs
+        for i in range(self.info['n_shared_outs']):
+            output.append(outer_inp_idx)
+            outer_inp_idx += 1
+
+        # No inner input corresponds to the outer nitsot inputs but they still
+        # need to be counted
+        outer_inp_idx += self.info['n_nit_sot']
+
+        # Handle non-sequences inputs
+        nb_nonseqs_inputs = len(self.inputs) - len(output)
+        for i in range(nb_nonseqs_inputs):
+            output.append(outer_inp_idx)
+            outer_inp_idx += 1
+
+        return output
+
     ### GRAD FUNCTION
     def grad(self, inputs, dC_douts):
         outs = self(*inputs)


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
@@ -817,6 +817,25 @@ def test_using_taps_sequence(self):
         rval = theano.function([x], y, updates=updates)(inp)
         assert numpy.all(rval == inp[:-1])
 
+    def test_connection_pattern(self):
+        """Test connection_pattern() in the presence of recurrent outputs
+        with multiple taps.
+
+        This test refers to a bug signaled on the theano-users mailing list
+        on March 10 2015 by David Schneider-Joseph.
+        """
+        def fn(a_m2, a_m1, b_m2, b_m1):
+            return a_m1, b_m1
+
+        a0 = theano.shared(numpy.arange(2))
+        b0 = theano.shared(numpy.arange(2))
+
+        (a, b), _ = theano.scan(fn,
+                        outputs_info=[{'initial': a0, 'taps': [-2, -1]},
+                                      {'initial': b0, 'taps': [-2, -1]}],
+                        n_steps=2)
+        tensor.grad(a[-1], a0)
+
     # simple rnn, one input, one state, weights for each; input/state are
     # vectors, weights are scalars; using shared variables and past
     # taps (sequences and outputs)