class_docstr: Parameters
  ----------
  inputs
      Inputs of the inner function of scan.
  outputs
      Outputs of the inner function of scan.
  info
      Dictionary containing different properties of the scan op (like number
      of different types of arguments, name, mode, if it should run on GPU or
      not, etc.).
  typeConstructor
      Function that constructs an equivalent to Theano TensorType.
  
  Notes
  -----
  ``typeConstructor`` had been added to refactor how
  Theano deals with the GPU. If it runs on the GPU, scan needs
  to construct certain outputs (those who reside in the GPU
  memory) as the GPU-specific type.  However we can not import
  gpu code in this file (as it is in sandbox, and not available
  on each machine) so the workaround is that the GPU
  optimization passes to the constructor of this class a
  function that is able to construct a GPU type. This way the
  class Scan does not need to be aware of the details for the
  GPU, it just constructs any tensor using this function (which
  by default constructs normal tensors).
class_docstr_tok: ["Parameters","--","--","--","--","--","inputs","Inputs","of","the","inner","function","of","scan",".","outputs","Outputs","of","the","inner","function","of","scan",".","info","Dictionary","containing","different","properties","of","the","scan","op","(","like","number","of","different","types","of","arguments",",","name",",","mode",",","if","it","should","run","on","GPU","or","not",",","etc.",")",".","typeConstructor","Function","that","constructs","an","equivalent","to","Theano","TensorType",".","Notes","--","--","-","``","typeConstructor","``","had","been","added","to","refactor","how","Theano","deals","with","the","GPU",".","If","it","runs","on","the","GPU",",","scan","needs","to","construct","certain","outputs","(","those","who","reside","in","the","GPU","memory",")","as","the","GPU-specific","type",".","However","we","can","not","import","gpu","code","in","this","file","(","as","it","is","in","sandbox",",","and","not","available","on","each","machine",")","so","the","workaround","is","that","the","GPU","optimization","passes","to","the","constructor","of","this","class","a","function","that","is","able","to","construct","a","GPU","type",".","This","way","the","class","Scan","does","not","need","to","be","aware","of","the","details","for","the","GPU",",","it","just","constructs","any","tensor","using","this","function","(","which","by","default","constructs","normal","tensors",")","."]
code: def L_op(self, inputs, outs, dC_douts):
      if not isinstance(outs, (list, tuple)):
          outs = [outs]
      # `grad_step` equals the number of steps the original scan node has
      # done (if the original scan is a while loop than this number is the
      # length of the output sequence)
      # We do not know what kind of outputs the original scan has, so we
      # try first to see if it has a nit_sot output, then a sit_sot and
      # then a mit_sot
      if self.n_nit_sot > 0:
          grad_steps = self.outer_nitsot_outs(outs)[0].shape[0]
      elif self.n_sit_sot > 0:
          grad_steps = self.outer_sitsot_outs(outs)[0].shape[0] - 1
      elif self.n_mit_sot > 0:
          grad_steps = (
              self.outer_mitsot_outs(outs)[0].shape[0]
              + self.mintaps[self.n_mit_mot]
          )
      else:
          grad_steps = inputs[0]
      if self.as_while:
          n_steps = outs[0].shape[0]
  
      # Restrict the number of grad steps according to
      # self.truncate_gradient
      if self.truncate_gradient != -1:
          grad_steps = tensor.minimum(
              grad_steps, self.truncate_gradient
          )
  
      self_inputs = self.inputs
      self_outputs = self.outputs
      # differentiable inputs
      diff_inputs = (
          self.inner_seqs(self_inputs)
          + self.inner_mitmot(self_inputs)
          + self.inner_mitsot(self_inputs)
          + self.inner_sitsot(self_inputs)
          + self.inner_non_seqs(self_inputs)
      )
      diff_outputs = (
          self.inner_mitmot_outs(self_outputs)
          + self.inner_mitsot_outs(self_outputs)
          + self.inner_sitsot_outs(self_outputs)
          + self.inner_nitsot_outs(self_outputs)
      )
      scan_node = outs[0].owner
      connection_pattern = self.connection_pattern(scan_node)
  
      def get_inp_idx(iidx):
          if iidx < self.n_seqs:
              return 1 + iidx
          oidx = 1 + self.n_seqs
          iidx = iidx - self.n_seqs
          for taps in self.mitmot_taps():
              if len(taps) > iidx:
                  return oidx
              else:
                  oidx += 1
                  iidx -= len(taps)
          for taps in self.mitsot_taps():
              if len(taps) > iidx:
                  return oidx
              else:
                  oidx += 1
                  iidx -= len(taps)
  
          if iidx < self.info["n_sit_sot"]:
              return oidx + iidx
          else:
              return oidx + iidx + self.info["n_nit_sot"]
  
      def get_out_idx(iidx):
          oidx = 0
          for taps in self.mitmot_out_taps():
              if len(taps) > iidx:
                  return oidx
              else:
                  oidx += 1
                  iidx -= len(taps)
          return oidx + iidx
  
      def compute_all_gradients(known_grads):
          y_s = known_grads.keys()
          g_y_s = known_grads.values()
  
          for g_y in g_y_s:
              if str(g_y.dtype) in tensor.integer_dtypes:
                  raise TypeError(
                      "Gradients may never be integers but g_y "
                      "has type " + str(g_y.type)
                  )
  
          out_indices = [
              get_out_idx(self_outputs.index(y)) for y in y_s
          ]
  
          connected_inputs = [
              i
              for i in range(len(scan_node.inputs))
              if any(
                  [connection_pattern[i][odx] for odx in out_indices]
              )
          ]
  
          wrt = [
              x
              for x in theano.gof.graph.inputs(y_s)
              if (x in diff_inputs)
              and get_inp_idx(self_inputs.index(x)) in connected_inputs
          ]
          gmp = OrderedDict()
  
          # Required in case there is a pair of variables X and Y, with X
          # used to compute Y, for both of which there is an external
          # gradient signal. Without this, the total gradient signal on X
          # will be the external gradient  signalknown_grads[X]. With this,
          # it will be the sum of the external gradient signal and the
          # gradient obtained by propagating Y's external gradient signal
          # to X.
          known_grads = OrderedDict(
              [(k.copy(), v) for (k, v) in known_grads.items()]
          )
  
          grads = gradient.grad(
              cost=None,
              known_grads=known_grads,
              wrt=wrt,
              consider_constant=wrt,
              disconnected_inputs="ignore",
              return_disconnected="None",
              null_gradients="return",
          )
  
          for i in range(len(wrt)):
              gmp[wrt[i]] = grads[i]
  
          rval = [gmp.get(p, None) for p in diff_inputs]
          return rval
  
      dC_dinps_t = [None for inp in diff_inputs]
      disconnected_dC_dinps_t = [True for inp in diff_inputs]
      dC_dXts = []
      Xts = []
      for idx, Xt in enumerate(diff_outputs):
          # We are looking for x[t-1] for a given x[t]
          if idx >= self.n_mit_mot_outs:
              Xt_placeholder = safe_new(Xt)
              Xts.append(Xt_placeholder)
  
          # Different processing based on whether Xt is a nitsot output
          # or not. NOTE : This cannot be done by using
          # "if Xt not in self.inner_nitsot_outs(self_outputs)" because
          # the exact same variable can be used as multiple outputs.
          idx_nitsot_start = (
              self.info["n_mit_mot"]
              + self.info["n_mit_sot"]
              + self.info["n_sit_sot"]
          )
          idx_nitsot_end = idx_nitsot_start + self.info["n_nit_sot"]
          if idx < idx_nitsot_start or idx >= idx_nitsot_end:
              # What we do here is loop through dC_douts and collect all
              # those that are connected to the specific one and do an
              # upcast on all of their dtypes to get the dtype for this
              # specific output. Deciding if the gradient with this
              # specific previous step is defined or not is done somewhere
              # else.
              dtypes = []
              states = (
                  self.inner_mitmot(self_inputs)
                  + self.inner_mitsot(self_inputs)
                  + self.inner_sitsot(self_inputs)
              )
  
              for pos, inp in enumerate(states):
                  if inp in theano.gof.graph.inputs([Xt]):
                      # Get the index of the outer output that to which
                      # the state variable 'inp' corresponds.
                      outer_oidx = self.var_mappings[
                          "outer_out_from_inner_inp"
                      ][self.n_seqs + pos]
  
                      if not isinstance(
                          dC_douts[outer_oidx].type, DisconnectedType
                      ):
                          dtypes.append(dC_douts[outer_oidx].dtype)
              if dtypes:
                  new_dtype = theano.scalar.upcast(*dtypes)
              else:
                  new_dtype = theano.config.floatX
              dC_dXt = safe_new(Xt, dtype=new_dtype)
          else:
              if isinstance(dC_douts[idx].type, DisconnectedType):
                  continue
              dC_dXt = safe_new(dC_douts[idx][0])
          dC_dXts.append(dC_dXt)
  
      known_grads = OrderedDict()
      dc_dxts_idx = 0
      for i in range(len(diff_outputs)):
          if i < idx_nitsot_start or i >= idx_nitsot_end:
              if diff_outputs[i] in known_grads:
                  known_grads[diff_outputs[i]] += dC_dXts[dc_dxts_idx]
              else:
                  known_grads[diff_outputs[i]] = dC_dXts[dc_dxts_idx]
              dc_dxts_idx += 1
          else:
              if isinstance(dC_douts[i].type, DisconnectedType):
                  continue
              else:
                  if diff_outputs[i] in known_grads:
                      known_grads[diff_outputs[i]] += dC_dXts[
                          dc_dxts_idx
                      ]
                  else:
                      known_grads[diff_outputs[i]] = dC_dXts[
                          dc_dxts_idx
                      ]
                  dc_dxts_idx += 1
      dC_dinps_t = compute_all_gradients(known_grads)
  
      # mask inputs that get no gradients
      for dx in xrange(len(dC_dinps_t)):
          if not dC_dinps_t[dx]:
              dC_dinps_t[dx] = tensor.zeros_like(diff_inputs[dx])
          else:
              disconnected_dC_dinps_t[dx] = False
              for Xt, Xt_placeholder in zip(
                  diff_outputs[self.n_mit_mot_outs :], Xts
              ):
                  tmp = forced_replace(
                      dC_dinps_t[dx], Xt, Xt_placeholder
                  )
                  dC_dinps_t[dx] = tmp
  
      # construct dX_dtm1
      dC_dXtm1s = []
      for pos, x in enumerate(dC_dinps_t[self.n_seqs :]):
          # Get the index of the first inner input corresponding to the
          # pos-ieth inner input state
          idxs = self.var_mappings["inner_out_from_inner_inp"][
              self.n_seqs + pos
          ]
  
          # Check if the pos-th input is associated with one of the
          # recurrent states
          x_is_state = pos < sum([len(t) for t in self.tap_array])
  
          if x_is_state and len(idxs) > 0:
              opos = idxs[0]
              dC_dXtm1s.append(safe_new(dC_dXts[opos]))
              if hasattr(x, "dtype") and x.dtype != dC_dXts[opos].dtype:
                  dC_dinps_t[pos + self.n_seqs] = x.astype(
                      dC_dXts[opos].dtype
                  )
          else:
              dC_dXtm1s.append(safe_new(x))
  
      for dx, dC_dXtm1 in enumerate(dC_dXtm1s):
          if isinstance(dC_dinps_t[dx + self.n_seqs].type, NullType):
              # The accumulated gradient is undefined
              pass
          elif isinstance(dC_dXtm1.type, NullType):
              # The new gradient is undefined, this makes the accumulated
              # gradient undefined as weell
              dC_dinps_t[dx + self.n_seqs] = dC_dXtm1
          else:
              dC_dinps_t[dx + self.n_seqs] += dC_dXtm1
      # Construct scan op
      # Seqs
      if self.as_while:
          # equivalent to x[:n_steps][::-1]
          outer_inp_seqs = [
              x[n_steps - 1 :: -1] for x in inputs[1 : 1 + self.n_seqs]
          ]
      else:
          outer_inp_seqs = [
              x[::-1] for x in inputs[1 : 1 + self.n_seqs]
          ]
      for idx in xrange(self.n_mit_mot + self.n_mit_sot):
          mintap = np.min(self.tap_array[idx])
          if idx < self.n_mit_mot:
              outmaxtap = np.max(self.mitmot_out_taps()[idx])
          else:
              outmaxtap = 0
          seq = outs[idx]
          for k in self.tap_array[idx]:
              if outmaxtap - k != 0:
                  nw_seq = seq[k - mintap : -(outmaxtap - k)][::-1]
              else:
                  nw_seq = seq[k - mintap :][::-1]
              outer_inp_seqs.append(nw_seq)
      outer_inp_seqs += [
          x[:-1][::-1] for x in self.outer_sitsot_outs(outs)
      ]
      for x in self.outer_nitsot_outs(dC_douts):
          if not isinstance(x.type, DisconnectedType):
              if self.as_while:
                  # equivalent to x[:n_steps][::-1]
                  outer_inp_seqs.append(x[n_steps - 1 :: -1])
              else:
                  outer_inp_seqs.append(x[::-1])
  
      if hasattr(inputs[0].tag, "test_value"):
          # Here we tests that the new scan input sequence all have
          # the same shape[0]. This is a properties that the scan()
          # fct add and we want to keep it for all Scan op.  This is
          # used in T_Scan.test_grad_multiple_outs_taps to test
          # that.
          if self.as_while:
              n = n_steps.tag.test_value
          else:
              n = inputs[0].tag.test_value
          for taps, x in zip(
              self.mitsot_taps(), self.outer_mitsot_outs(outs)
          ):
              mintap = np.min(taps)
              if hasattr(x[::-1][:mintap], "test_value"):
                  assert x[::-1][:mintap].tag.test_value.shape[0] == n
          for x in self.outer_sitsot_outs(outs):
              if hasattr(x[::-1][:-1].tag, "test_value"):
                  assert x[::-1][:-1].tag.test_value.shape[0] == n
          for x in self.outer_nitsot_outs(outs):
              if hasattr(x[::-1].tag, "test_value"):
                  if self.as_while:
                      assert (
                          x[n_steps - 1 :: -1].tag.test_value.shape[0]
                          == n
                      )
                  else:
                      assert x[::-1].tag.test_value.shape[0] == n
      outer_inp_seqs += [
          x[::-1][: np.min(taps)]
          for taps, x in zip(
              self.mitsot_taps(), self.outer_mitsot_outs(outs)
          )
      ]
      outer_inp_seqs += [
          x[::-1][:-1] for x in self.outer_sitsot_outs(outs)
      ]
      outer_inp_seqs += [x[::-1] for x in self.outer_nitsot_outs(outs)]
  
      # Restrict the length of the outer sequences to the number of grad
      # steps
      outer_inp_seqs = [s_[:grad_steps] for s_ in outer_inp_seqs]
  
      inner_inp_seqs = self.inner_seqs(self_inputs)
      inner_inp_seqs += self.inner_mitmot(self_inputs)
      inner_inp_seqs += self.inner_mitsot(self_inputs)
      inner_inp_seqs += self.inner_sitsot(self_inputs)
      inner_inp_seqs += self.inner_nitsot_outs(dC_dXts)
      inner_inp_seqs += Xts
      # mitmot
      outer_inp_mitmot = []
      inner_inp_mitmot = []
      inner_out_mitmot = []
      mitmot_inp_taps = []
      mitmot_out_taps = []
      type_outs = []
      out_pos = 0
      ins_pos = self.n_seqs
      n_mitmot_outs = 0
      n_mitmot_inps = 0
  
      for idx in xrange(self.n_mit_mot):
          if isinstance(dC_douts[idx].type, DisconnectedType):
              out = outs[idx]
              outer_inp_mitmot.append(tensor.zeros_like(out))
          else:
              outer_inp_mitmot.append(dC_douts[idx][::-1])
          mitmot_inp_taps.append([])
          mitmot_out_taps.append([])
          undefined_msg = None
          through_shared = False
          disconnected = True
  
          for jdx in xrange(len(self.mit_mot_out_slices[idx])):
              inner_inp_mitmot.append(dC_dXts[out_pos])
              mitmot_inp_taps[idx].append(
                  -self.mit_mot_out_slices[idx][jdx]
              )
              n_mitmot_inps += 1
              out_pos += 1
  
          for jdx in xrange(len(self.tap_array[idx])):
              tap = -self.tap_array[idx][jdx]
  
              # Only create a new inner input if there is not already one
              # associated with this input tap
              if tap not in mitmot_inp_taps[idx]:
                  inner_inp_mitmot.append(
                      dC_dXtm1s[ins_pos - self.n_seqs]
                  )
  
              if isinstance(dC_dinps_t[ins_pos].type, NullType):
                  # We cannot use Null in the inner graph, so we
                  # use a zero tensor of the appropriate shape instead.
                  inner_out_mitmot.append(
                      tensor.zeros(
                          diff_inputs[ins_pos].shape,
                          dtype=theano.config.floatX,
                      )
                  )
                  undefined_msg = dC_dinps_t[ins_pos].type.why_null
              else:
                  new_inner_out_mitmot = dC_dinps_t[ins_pos]
  
                  # If there is already an inner input associated with that
                  # input tap, make sure the computation of the new output
                  # uses it instead of the input it's currently using
                  if tap in mitmot_inp_taps[idx]:
                      to_replace = dC_dXtm1s[ins_pos - self.n_seqs]
                      replacement_idx = len(
                          mitmot_inp_taps[idx]
                      ) - mitmot_inp_taps[idx].index(tap)
                      replacement = inner_inp_mitmot[-replacement_idx]
  
                      self.tap_array[idx]
                      new_inner_out_mitmot = theano.clone(
                          new_inner_out_mitmot,
                          replace=[(to_replace, replacement)],
                      )
  
                  inner_out_mitmot.append(new_inner_out_mitmot)
  
              if not disconnected_dC_dinps_t[ins_pos]:
                  disconnected = False
  
              for _sh in self.inner_shared(self_inputs):
                  if _sh in gof.graph.inputs([dC_dinps_t[ins_pos]]):
                      through_shared = True
  
              ins_pos += 1
              n_mitmot_outs += 1
              mitmot_out_taps[idx].append(-self.tap_array[idx][jdx])
  
              # Only add the tap as a new input tap if needed
              if tap not in mitmot_inp_taps[idx]:
                  n_mitmot_inps += 1
                  mitmot_inp_taps[idx].append(-self.tap_array[idx][jdx])
  
          if undefined_msg:
              type_outs.append(undefined_msg)
          elif through_shared:
              type_outs.append("through_shared")
          elif disconnected:
              type_outs.append("disconnected")
          else:
              type_outs.append("connected")
  
      offset = self.n_mit_mot
      for idx in xrange(self.n_mit_sot):
          if isinstance(dC_douts[idx + offset].type, DisconnectedType):
              outer_inp_mitmot.append(outs[idx + offset].zeros_like())
          else:
              outer_inp_mitmot.append(dC_douts[idx + offset][::-1])
          mitmot_inp_taps.append([])
          mitmot_out_taps.append([])
          idx_tap = idx + self.n_mit_mot
          inner_inp_mitmot.append(dC_dXts[out_pos])
          out_pos += 1
          n_mitmot_inps += 1
          undefined_msg = None
          through_shared = False
          disconnected = True
          mitmot_inp_taps[idx + offset].append(0)
          for jdx in xrange(len(self.tap_array[idx_tap])):
              inner_inp_mitmot.append(dC_dXtm1s[ins_pos - self.n_seqs])
  
              if isinstance(dC_dinps_t[ins_pos].type, NullType):
                  # We cannot use Null in the inner graph, so we
                  # use a zero tensor of the appropriate shape instead.
                  inner_out_mitmot.append(
                      tensor.zeros(
                          diff_inputs[ins_pos].shape,
                          dtype=theano.config.floatX,
                      )
                  )
                  undefined_msg = dC_dinps_t[ins_pos].type.why_null
              else:
                  inner_out_mitmot.append(dC_dinps_t[ins_pos])
  
              mitmot_inp_taps[idx + offset].append(
                  -self.tap_array[idx_tap][jdx]
              )
              mitmot_out_taps[idx].append(-self.tap_array[idx_tap][jdx])
              if not disconnected_dC_dinps_t[ins_pos]:
                  disconnected = False
              for _sh in self.inner_shared(self_inputs):
                  if _sh in gof.graph.inputs([dC_dinps_t[ins_pos]]):
                      through_shared = True
  
              n_mitmot_inps += 1
              ins_pos += 1
              n_mitmot_outs += 1
  
          if undefined_msg:
              type_outs.append(undefined_msg)
          elif through_shared:
              type_outs.append("through_shared")
          elif disconnected:
              type_outs.append("disconnected")
          else:
              type_outs.append("connected")
  
      offset += self.n_mit_sot
      for idx in xrange(self.n_sit_sot):
          mitmot_inp_taps.append([0, 1])
          mitmot_out_taps.append([1])
          through_shared = False
          if not isinstance(
              dC_douts[idx + offset].type, DisconnectedType
          ):
              outer_inp_mitmot.append(dC_douts[idx + offset][::-1])
          else:
              if isinstance(dC_dinps_t[ins_pos].type, NullType):
                  # Cannot use dC_dinps_t[ins_pos].dtype, so we use
                  # floatX instead, as it is a dummy value that will not
                  # be used anyway.
                  outer_inp_mitmot.append(
                      tensor.zeros(
                          outs[idx + offset].shape,
                          dtype=theano.config.floatX,
                      )
                  )
              else:
                  outer_inp_mitmot.append(
                      tensor.zeros(
                          outs[idx + offset].shape,
                          dtype=dC_dinps_t[ins_pos].dtype,
                      )
                  )
  
          if isinstance(dC_dinps_t[ins_pos].type, NullType):
              # We cannot use Null in the inner graph, so we
              # use a zero tensor of the appropriate shape instead.
              inner_out_mitmot.append(
                  tensor.zeros(
                      diff_inputs[ins_pos].shape,
                      dtype=theano.config.floatX,
                  )
              )
          else:
              inner_out_mitmot.append(dC_dinps_t[ins_pos])
  
          for _sh in self.inner_shared(self_inputs):
              if _sh in gof.graph.inputs([dC_dinps_t[ins_pos]]):
                  through_shared = True
  
          if isinstance(dC_dinps_t[ins_pos].type, NullType):
              type_outs.append(dC_dinps_t[ins_pos].type.why_null)
          elif through_shared:
              type_outs.append("through_shared")
          elif disconnected_dC_dinps_t[ins_pos]:
              type_outs.append("disconnected")
          else:
              type_outs.append("connected")
  
          inner_inp_mitmot += [
              dC_dXts[out_pos],
              dC_dXtm1s[ins_pos - self.n_seqs],
          ]
          n_mitmot_outs += 1
          out_pos += 1
          ins_pos += 1
          n_mitmot_inps += 2
  
      n_nit_sot = self.n_seqs
      inner_out_nitsot = dC_dinps_t[: self.n_seqs]
      inner_out_sitsot = dC_dinps_t[ins_pos:]
      for _p, vl in enumerate(inner_out_sitsot):
          through_shared = False
          for _sh in self.inner_shared(self_inputs):
              if _sh in gof.graph.inputs([vl]):
                  through_shared = True
          if isinstance(vl.type, NullType):
              type_outs.append(vl.type.why_null)
              # Replace the inner output with a zero tensor of
              # the right shape
              inner_out_sitsot[_p] = tensor.zeros(
                  diff_inputs[ins_pos + _p].shape,
                  dtype=theano.config.floatX,
              )
          elif through_shared:
              type_outs.append("through_shared")
          elif disconnected_dC_dinps_t[_p + ins_pos]:
              type_outs.append("disconnected")
          else:
              type_outs.append("connected")
  
      for _p, vl in enumerate(inner_out_nitsot):
          through_shared = False
          for _sh in self.inner_shared(self_inputs):
              if _sh in gof.graph.inputs([vl]):
                  through_shared = True
          if isinstance(vl.type, NullType):
              type_outs.append(vl.type.why_null)
              # Replace the inner output with a zero tensor of
              # the right shape
              inner_out_nitsot[_p] = tensor.zeros(
                  diff_inputs[_p].shape, dtype=theano.config.floatX
              )
  
          if through_shared:
              type_outs.append("through_shared")
          elif disconnected_dC_dinps_t[_p]:
              type_outs.append("disconnected")
          else:
              type_outs.append("connected")
  
      inner_inp_sitsot = dC_dXtm1s[ins_pos - self.n_seqs :]
      outer_inp_sitsot = []
      for _idx, y in enumerate(inner_inp_sitsot):
          x = self.outer_non_seqs(inputs)[_idx]
          if isinstance(y.type, NullType):
              # Cannot use dC_dXtm1s.dtype, so we use floatX instead.
              outer_inp_sitsot.append(
                  tensor.zeros(
                      [grad_steps + 1]
                      + [x.shape[i] for i in xrange(x.ndim)],
                      dtype=theano.config.floatX,
                  )
              )
              # replace y by a zero tensor of the right shape
              inner_inp_sitsot[_idx] = tensor.zeros(
                  diff_inputs[ins_pos + _idx].shape,
                  dtype=theano.config.floatX,
              )
  
          else:
              outer_inp_sitsot.append(
                  tensor.zeros(
                      [grad_steps + 1]
                      + [x.shape[i] for i in xrange(x.ndim)],
                      dtype=y.dtype,
                  )
              )
  
      n_sitsot_outs = len(outer_inp_sitsot)
      new_tap_array = mitmot_inp_taps + [
          [-1] for k in xrange(n_sitsot_outs)
      ]
  
      info = OrderedDict()
      info["n_seqs"] = len(outer_inp_seqs)
      info["n_mit_sot"] = 0
      info["tap_array"] = new_tap_array
      info["gpua"] = False
      info["n_mit_mot"] = len(outer_inp_mitmot)
      info["n_mit_mot_outs"] = n_mitmot_outs
      info["mit_mot_out_slices"] = mitmot_out_taps
      info["truncate_gradient"] = self.truncate_gradient
      info["n_sit_sot"] = n_sitsot_outs
      info["n_shared_outs"] = 0
      info["n_nit_sot"] = n_nit_sot
      info["as_while"] = False
      info["profile"] = self.profile
      info["destroy_map"] = OrderedDict()
      if self.name:
          info["name"] = "grad_of_" + self.name
      else:
          info["name"] = None
      info["mode"] = self.mode
      info["allow_gc"] = self.allow_gc
  
      outer_inputs = (
          [grad_steps]
          + outer_inp_seqs
          + outer_inp_mitmot
          + outer_inp_sitsot
          + [
              n_steps if self.as_while else inputs[0]
              for _ in xrange(n_nit_sot)
          ]
          + self.outer_shared(inputs)
          + self.outer_non_seqs(inputs)
      )
  
      inner_gfn_ins = (
          inner_inp_seqs
          + inner_inp_mitmot
          + inner_inp_sitsot
          + self.inner_shared(self_inputs)
          + self.inner_non_seqs(self_inputs)
      )
      inner_gfn_outs = (
          inner_out_mitmot + inner_out_sitsot + inner_out_nitsot
      )
  
      local_op = Scan(inner_gfn_ins, inner_gfn_outs, info)
      outputs = local_op(*outer_inputs)
      if type(outputs) not in (list, tuple):
          outputs = [outputs]
      # Re-order the gradients correctly
      gradients = [DisconnectedType()()]
  
      offset = (
          self.n_mit_mot
          + self.n_mit_sot
          + self.n_sit_sot
          + n_sitsot_outs
      )
      for p, (x, t) in enumerate(
          zip(
              outputs[offset : offset + self.n_seqs],
              type_outs[offset : offset + self.n_seqs],
          )
      ):
          if t == "connected":
              # If the forward scan is in as_while mode, we need to pad
              # the gradients, so that they match the size of the input
              # sequences.
              if self.as_while:
                  n_zeros = inputs[0] - n_steps
                  shp = (n_zeros,)
                  if x.ndim > 1:
                      shp = shp + tuple(
                          x.shape[i] for i in range(1, x.ndim)
                      )
                  z = tensor.zeros(shp, dtype=x.dtype)
                  x = tensor.concatenate([x[::-1], z], axis=0)
                  gradients.append(x)
              else:
                  gradients.append(x[::-1])
          elif t == "disconnected":
              gradients.append(DisconnectedType()())
          elif t == "through_shared":
              gradients.append(
                  grad_undefined(
                      self,
                      p + 1,
                      inputs[p + 1],
                      "Depends on a shared variable",
                  )
              )
          else:
              # t contains the "why_null" string of a NullType
              gradients.append(NullType(t)())
  
      end = self.n_mit_mot + self.n_mit_sot + self.n_sit_sot
      for p, (x, t) in enumerate(zip(outputs[:end], type_outs[:end])):
          if t == "connected":
              # If the forward scan is in as_while mode, we need to pad
              # the gradients, so that they match the size of the input
              # sequences.
              if self.as_while:
                  n_zeros = inputs[0] - grad_steps
                  shp = (n_zeros,)
                  if x.ndim > 1:
                      shp = shp + tuple(
                          x.shape[i] for i in range(1, x.ndim)
                      )
                  z = tensor.zeros(shp, dtype=x.dtype)
                  x = tensor.concatenate([x[::-1], z], axis=0)
                  gradients.append(x)
              else:
                  gradients.append(x[::-1])
          elif t == "disconnected":
              gradients.append(DisconnectedType()())
          elif t == "through_shared":
              gradients.append(
                  grad_undefined(
                      self,
                      p + 1 + self.n_seqs,
                      inputs[p + 1 + self.n_seqs],
                      "Depends on a shared variable",
                  )
              )
          else:
              # t contains the "why_null" string of a NullType
              gradients.append(NullType(t)())
  
      start = len(gradients)
      node = outs[0].owner
      for idx in xrange(self.n_shared_outs):
          disconnected = True
          connected_flags = self.connection_pattern(node)[idx + start]
          for dC_dout, connected in zip(dC_douts, connected_flags):
              if (
                  not isinstance(dC_dout.type, DisconnectedType)
                  and connected
              ):
                  disconnected = False
          if disconnected:
              gradients.append(DisconnectedType()())
          else:
              gradients.append(
                  grad_undefined(
                      self,
                      idx,
                      inputs[idx],
                      "Shared Variable with update",
                  )
              )
  
      start = len(gradients)
      gradients += [
          DisconnectedType()() for _ in xrange(self.n_nit_sot)
      ]
      begin = end
  
      end = begin + n_sitsot_outs
      for p, (x, t) in enumerate(
          zip(outputs[begin:end], type_outs[begin:end])
      ):
          if t == "connected":
              gradients.append(x[-1])
          elif t == "disconnected":
              gradients.append(DisconnectedType()())
          elif t == "through_shared":
              gradients.append(
                  grad_undefined(
                      self,
                      p + begin + 1,
                      inputs[p + begin + 1],
                      "Depends on a shared variable",
                  )
              )
          else:
              # t contains the "why_null" string of a NullType
              gradients.append(NullType(t)())
  
      # Mask disconnected gradients
      # Ideally we would want to assert that the gradients we are
      # replacing do indeed evaluate to 0, though that is not practical
      # from a computational point of view
      # The gradients of scan are computed replacing Disconnected with 0,
      # because through the recurrence they can become nonzero
      for idx in xrange(len(gradients)):
          disconnected = True
          for kdx in xrange(len(node.outputs)):
              if connection_pattern[idx][kdx] and not isinstance(
                  dC_douts[kdx].type, DisconnectedType
              ):
                  disconnected = False
          if disconnected:
              gradients[idx] = DisconnectedType()()
      return gradients
  
imports: from __future__ import absolute_import, print_function, division
  import copy
  import itertools
  import logging
  import time
  from collections import OrderedDict
  import numpy
  from six import iteritems, integer_types, raise_from
  from six.moves import xrange
  import theano
  from theano.compile import function, In, Out
  from theano.compile.mode import AddFeatureOptimizer
  from theano import compile, config, gradient, gof, tensor
  from theano.gof import PureOp, Apply
  from theano.gof.graph import io_connection_pattern
  from theano.gof.toolbox import NoOutputFromInplace
  from theano.compat import izip
  from theano.tensor import as_tensor_variable, TensorType
  from theano.tensor.opt import Shape_i
  from theano.gradient import grad_undefined, DisconnectedType, NullType
  from six import string_types
  from theano.compile.profiling import ScanProfileStats
  from theano.scan_module import scan_utils
  from theano.scan_module.scan_utils import safe_new, forced_replace
label_desc: Use image node_id 39 for calling the Scan obj's underlying member method code with example usage: obj.L_op(inputs, outs, dC_douts) and returns: gradients, unknown, rval, unknown, unknown, unknown, oidx, oidx, oidx