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",")","."]
docstr: Conventions:
      inner_X - the variable corresponding to X in the inner function
                of scan (the lambda function executed at every time
                step)
      outer_X - the variable corresponding to X in the outer graph,
                i.e. the main graph (where the scan op lives)
      inner_X_out - the variable representing the new value of X after
                    executing one step of scan (i.e. outputs given by
                    the inner function)
docstr_tok: ["Conventions",":","inner_X","-","the","variable","corresponding","to","X","in","the","inner","function","of","scan","(","the","lambda","function","executed","at","every","time","step",")","outer_X","-","the","variable","corresponding","to","X","in","the","outer","graph",",","i.e",".","the","main","graph","(","where","the","scan","op","lives",")","inner_X_out","-","the","variable","representing","the","new","value","of","X","after","executing","one","step","of","scan","(","i.e",".","outputs","given","by","the","inner","function",")"]
code: def make_node(self, *inputs):
      """
      Conventions:
          inner_X - the variable corresponding to X in the inner function
                    of scan (the lambda function executed at every time
                    step)
          outer_X - the variable corresponding to X in the outer graph,
                    i.e. the main graph (where the scan op lives)
          inner_X_out - the variable representing the new value of X after
                        executing one step of scan (i.e. outputs given by
                        the inner function)
  
      """
      assert np.all(isinstance(i, gof.Variable) for i in inputs)
      # Check that the number of inputs to the Scan node corresponds to
      # the number of inputs of the inner function of scan
      n_outer_ins = len(inputs) - len(self.outer_nitsot(inputs)) - 1
      n_inner_ins = (
          len(self.inner_seqs(self.inputs))
          + len(self.mitmot_taps())
          + len(self.mitsot_taps())
          + len(self.inner_sitsot(self.inputs))
          + len(self.inner_shared(self.inputs))
          + len(self.inner_non_seqs(self.inputs))
      )
      assert n_outer_ins == n_inner_ins, (
          "The number of inputs given to the inner function of scan"
          " does not match the number of inputs given to scan."
      )
      # Force the inputs to be on the CPU
      new_inputs = [as_tensor_variable(inputs[0])]
      # assert dtype is consistent
      err_msg1 = (
          "When compiling the inner function of scan (the "
          "function called by scan in each of its iterations) "
          "the following error has been encountered: The "
          "%s %s (argument number %d) has dtype "
          "%s and %d dimension(s). The corresponding variable "
          "in the inner function of scan %s "
          "however has dtype %s and %d dimension(s). This "
          "variable in the inner function of scan should "
          "have the same dtype and one fewer dimension "
          "compared to its corresponding variable in the initial "
          "state (outputs_info in scan nomenclature). For example, "
          "if the inner function of scan returns a vector "
          "of size d and scan uses the values of "
          "the previous time-step, then the initial state in scan "
          "should be a matrix of shape (1, d). "
          "The first dimension of this "
          "matrix corresponds to the number of previous time-steps "
          "that scan uses in each of its iterations. "
          "In order to solve this issue if the two variable currently "
          "have the same dimensionality, you can increase the "
          "dimensionality of the varialbe in the initial state of scan "
          "by using dimshuffle or shape_padleft. "
      )
      err_msg2 = (
          "When compiling the inner function of scan the "
          "following error has been encountered: The "
          "initial state (`outputs_info` in scan nomenclature) "
          "of variable %s (argument number %d) "
          "has dtype %s, while the result of the inner function "
          "(`fn`) has dtype %s. This can happen if the inner "
          "function of scan results in an upcast or downcast."
      )
      err_msg3 = (
          "When compiling the inner function of scan (the "
          "function called by scan in each of its iterations) "
          "the following error has been encountered: The "
          "initial state (`outputs_info` in scan nomenclature) "
          "of variable %s (argument number %d) has %d dimension(s), "
          "while the corresponding variable in the result of the inner "
          "function of scan (`fn`) has %d dimension(s) (it should "
          "be one less than the initial state). For example, "
          "if the inner function of scan returns a vector "
          "of size d and scan uses the values of "
          "the previous time-step, then the initial state in scan "
          "should be a matrix of shape (1, d). "
          "The first dimension of this "
          "matrix corresponds to the number of previous time-steps "
          "that scan uses in each of its iterations. "
          "In order to solve this issue if the two varialbe currently "
          "have the same dimensionality, you can increase the "
          "dimensionality of the variable in the initial state of scan "
          "by using dimshuffle or shape_padleft. "
      )
  
      def check_broadcast(v1, v2):
          """Checks that the broadcast pattern of v1 and v2.
  
          Controls that the broadcast pattern of the variable provided as
          input to `scan` matches the broadcast pattern provided in
          `output_info`. It raises an error when they don't match. The
          typical case is when the user provides either the input or the
          `output_info` (but not both) with a dimension fixed to 1,
          which may wrongly be interpreted as broadcastable.
  
          """
          if not hasattr(v1, "broadcastable") and not hasattr(
              v2, "broadcastable"
          ):
              return
          msg = (
              "The broadcast pattern of the output of scan (%s) is "
              "inconsistent with the one provided in `output_info` "
              "(%s). The output on axis %d is `%r`, but it is `%r` on "
              "axis %d in `output_info`. This can happen if one of the "
              "dimension is fixed to 1 in the input, while it is still "
              "variable in the output, or vice-verca. You have to make "
              "them consistent, e.g. using theano.tensor."
              "{patternbroadcast,unbroadcast,addbroadcast}."
          )
          size = min(len(v1.broadcastable), len(v2.broadcastable))
          for n, (b1, b2) in enumerate(
              zip(v1.broadcastable[-size:], v2.broadcastable[-size:])
          ):
              if b1 != b2:
                  a1 = n + size - len(v1.broadcastable) + 1
                  a2 = n + size - len(v2.broadcastable) + 1
                  raise TypeError(
                      msg % (v1.type, v2.type, a1, b1, b2, a2)
                  )
  
      def format(var, as_var):
          """
          This functions ensures that ``out`` has the same dtype as
          ``inp`` as well as calling filter_variable to make sure
          they are both TensorType or GpuArrayType. It internally
          deals with the corner case where inp.ndim + 1 = out.ndim
  
          """
          if not hasattr(var, "dtype"):
              return var
          rval = var
          if rval.type.dtype != as_var.type.dtype:
              rval = rval.astype(as_var.type.dtype)
          if rval.ndim == as_var.ndim:
              rval = as_var.type.filter_variable(rval)
          else:
              tmp = as_var.type.clone(
                  broadcastable=(
                      tuple(var.broadcastable[:1])
                      + tuple(as_var.broadcastable)
                  )
              )
              rval = tmp.filter_variable(rval)
          return rval
  
      # Check if input sequences and variables representing a slice of
      # them have the same dtype
      argoffset = 0
      for inner_seq, outer_seq in zip(
          self.inner_seqs(self.inputs), self.outer_seqs(inputs)
      ):
          check_broadcast(outer_seq, inner_seq)
          new_inputs.append(format(outer_seq, as_var=inner_seq))
  
      argoffset += len(self.outer_seqs(inputs))
      # Check that this 3 things have the same dtype for mit_mot:
      #   - initial state of the output
      #   - variable representing an input slice of the output
      #   - variable representing an output slice of the output
      ipos = 0
      opos = 0
      inner_mitmot = self.inner_mitmot(self.inputs)
      inner_mitmot_outs = self.inner_mitmot_outs(self.outputs)
      for idx, (itaps, otaps, _outer_mitmot) in enumerate(
          zip(
              self.mitmot_taps(),
              self.mitmot_out_taps(),
              self.outer_mitmot(inputs),
          )
      ):
          outer_mitmot = format(
              _outer_mitmot, as_var=inner_mitmot[ipos]
          )
          new_inputs.append(outer_mitmot)
          for k in xrange(len(itaps)):
              if (
                  inner_mitmot[ipos + k].type.dtype
                  != outer_mitmot.type.dtype
                  or inner_mitmot[ipos + k].ndim
                  != outer_mitmot.ndim - 1
              ):
                  raise ValueError(
                      err_msg1
                      % (
                          "initial state (outputs_info"
                          " in scan nomenclature) ",
                          str(outer_mitmot),
                          argoffset + idx,
                          outer_mitmot.type.dtype,
                          outer_mitmot.type.ndim,
                          str(inner_mitmot[ipos + k]),
                          inner_mitmot[ipos + k].type.dtype,
                          inner_mitmot[ipos + k].type.ndim,
                      )
                  )
          ipos += len(itaps)
          for k in xrange(len(otaps)):
              if (
                  inner_mitmot_outs[opos + k].type.dtype
                  != outer_mitmot.type.dtype
              ):
                  raise ValueError(
                      err_msg2
                      % (
                          str(outer_mitmot),
                          argoffset + idx,
                          outer_mitmot.type.dtype,
                          inner_mitmot_outs[opos + k].type.dtype,
                      )
                  )
              if (
                  inner_mitmot_outs[opos + k].ndim
                  != outer_mitmot.ndim - 1
              ):
                  raise ValueError(
                      err_msg3
                      % (
                          str(outer_mitmot),
                          argoffset + idx,
                          outer_mitmot.ndim,
                          inner_mitmot_outs[opos + k].ndim,
                      )
                  )
          opos += len(otaps)
      argoffset += len(self.outer_mitmot(inputs))
      # Same checks as above but for outputs of type mit_sot
      ipos = 0
      inner_mitsots = self.inner_mitsot(self.inputs)
      for idx, (itaps, _outer_mitsot, inner_mitsot_out) in enumerate(
          zip(
              self.mitsot_taps(),
              self.outer_mitsot(inputs),
              self.inner_mitsot_outs(self.outputs),
          )
      ):
          outer_mitsot = format(
              _outer_mitsot, as_var=inner_mitsots[ipos]
          )
          new_inputs.append(outer_mitsot)
  
          for k in xrange(len(itaps)):
              if (
                  inner_mitsots[ipos + k].type.dtype
                  != outer_mitsot.type.dtype
                  or inner_mitsots[ipos + k].ndim
                  != outer_mitsot.ndim - 1
              ):
                  raise ValueError(
                      err_msg1
                      % (
                          "initial state (outputs_info"
                          " in scan nomenclature) ",
                          str(outer_mitsot),
                          argoffset + idx,
                          outer_mitsot.type.dtype,
                          outer_mitsot.type.ndim,
                          str(inner_mitsots[ipos + k]),
                          inner_mitsots[ipos + k].type.dtype,
                          inner_mitsots[ipos + k].type.ndim,
                      )
                  )
          ipos += len(itaps)
          if inner_mitsot_out.type.dtype != outer_mitsot.type.dtype:
              raise ValueError(
                  err_msg2
                  % (
                      str(outer_mitsot),
                      argoffset + idx,
                      outer_mitsot.type.dtype,
                      inner_mitsot_out.type.dtype,
                  )
              )
          if inner_mitsot_out.ndim != outer_mitsot.ndim - 1:
              raise ValueError(
                  err_msg3
                  % (
                      str(outer_mitsot),
                      argoffset + idx,
                      outer_mitsot.ndim,
                      inner_mitsot_out.ndim,
                  )
              )
  
      argoffset += len(self.outer_mitsot(inputs))
      # Same checks as above but for outputs of type sit_sot
      for idx, (
          inner_sitsot,
          _outer_sitsot,
          inner_sitsot_out,
      ) in enumerate(
          zip(
              self.inner_sitsot(self.inputs),
              self.outer_sitsot(inputs),
              self.inner_sitsot_outs(self.outputs),
          )
      ):
          outer_sitsot = format(_outer_sitsot, as_var=inner_sitsot)
          new_inputs.append(outer_sitsot)
          if inner_sitsot.ndim != outer_sitsot.ndim - 1:
              raise ValueError(
                  err_msg1
                  % (
                      "initial state (outputs_info"
                      " in scan nomenclature) ",
                      str(outer_sitsot),
                      argoffset + idx,
                      outer_sitsot.type.dtype,
                      outer_sitsot.type.ndim,
                      str(inner_sitsot),
                      inner_sitsot.type.dtype,
                      inner_sitsot.type.ndim,
                  )
              )
          if inner_sitsot_out.type.dtype != outer_sitsot.type.dtype:
              raise ValueError(
                  err_msg2
                  % (
                      str(outer_sitsot),
                      argoffset + idx,
                      outer_sitsot.type.dtype,
                      inner_sitsot_out.type.dtype,
                  )
              )
          if inner_sitsot_out.ndim != outer_sitsot.ndim - 1:
              raise ValueError(
                  err_msg3
                  % (
                      str(outer_sitsot),
                      argoffset + idx,
                      outer_sitsot.type.ndim,
                      inner_sitsot_out.type.ndim,
                  )
              )
  
      argoffset += len(self.outer_sitsot(inputs))
      # Check that the shared variable and their update rule have the same
      # dtype. Maybe even same type ?!
      for idx, (
          inner_shared,
          inner_shared_out,
          _outer_shared,
      ) in enumerate(
          zip(
              self.inner_shared(self.inputs),
              self.inner_shared_outs(self.outputs),
              self.outer_shared(inputs),
          )
      ):
          outer_shared = format(_outer_shared, as_var=inner_shared)
          new_inputs.append(outer_shared)
          if (
              hasattr(outer_shared, "dtype")
              and outer_shared.dtype != inner_shared_out.dtype
          ):
              raise ValueError(
                  err_msg2
                  % (
                      str(outer_shared),
                      idx + argoffset,
                      outer_shared.dtype,
                      inner_shared_out.dtype,
                  )
              )
          if (
              hasattr(outer_shared, "dtype")
              and outer_shared.ndim != inner_shared_out.ndim
          ):
              raise ValueError(
                  err_msg3
                  % (
                      str(outer_shared),
                      idx + argoffset,
                      outer_shared.ndim,
                      inner_shared_out.ndim,
                  )
              )
  
          if hasattr(outer_shared, "dtype") and (
              outer_shared.dtype != inner_shared.dtype
              or outer_shared.ndim != inner_shared.ndim
          ):
              raise ValueError(
                  err_msg1
                  % (
                      "initial state (outputs_info"
                      " in scan nomenclature) ",
                      str(outer_shared),
                      argoffset + idx,
                      outer_shared.dtype,
                      outer_shared.ndim,
                      str(inner_shared),
                      inner_shared.dtype,
                      inner_shared.ndim,
                  )
              )
      # We do not need to call `format` on outer_nisot arguments.
      # outer_nitsot stands for no input tap single output tap. This means
      # these are states that do not feed anything back in the recurrent
      # computation, and hence they do not have an initial state. The scan
      # node however receives an input for each such argument, the input
      # in this case is just a int saying how many steps of this output we
      # need to store. This input does not have the same dtype, nor is it the same
      # type of tensor as the output, it is always a scalar int.
      new_inputs += [
          as_tensor_variable(ons) for ons in self.outer_nitsot(inputs)
      ]
      for inner_nonseq, _outer_nonseq in zip(
          self.inner_non_seqs(self.inputs), self.outer_non_seqs(inputs)
      ):
          outer_nonseq = format(_outer_nonseq, as_var=inner_nonseq)
          new_inputs.append(outer_nonseq)
          if inner_nonseq.type != outer_nonseq.type:
              raise ValueError(
                  (
                      "Argument %s given to scan node does not"
                      " match its correspondance %s"
                  )
                  % (str(outer_nonseq), str(inner_nonseq))
              )
  
      for outer_nitsot in self.outer_nitsot(inputs):
          # For every nit_sot input we get as input a int/uint that
          # depicts the size in memory for that sequence. This feature is
          # used by truncated BPTT and by scan space optimization
          if (
              str(outer_nitsot.type.dtype) not in tensor.integer_dtypes
              or outer_nitsot.ndim != 0
          ):
              raise ValueError(
                  "For output %s you need to provide a " "scalar int !",
                  str(outer_nitsot),
              )
      assert len(new_inputs) == len(inputs)
  
      # The vector_seqs and vector_outs are just a workaround
      # strange NumPy behavior: vector_ndarray[int] return a NumPy
      # scalar and not a NumPy ndarray of 0 dimensions.
      def is_cpu_vector(s):
          return isinstance(s.type, tensor.TensorType) and s.ndim == 1
  
      self.vector_seqs = [
          is_cpu_vector(seq) for seq in new_inputs[1 : 1 + self.n_seqs]
      ]
      self.vector_outs = [
          is_cpu_vector(arg)
          for arg in new_inputs[
              1 + self.n_seqs : (1 + self.n_seqs + self.n_outs)
          ]
      ]
      self.vector_outs += [
          isinstance(t.type, tensor.TensorType) and t.ndim == 0
          for t in self.outer_nitsot_outs(self.outputs)
      ]
  
      apply_node = Apply(
          self, new_inputs, [t() for t in self.output_types]
      )
      return apply_node
  
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 4 for calling the Scan obj's underlying member method code with example usage: obj.make_node() and returns: apply_node, rval, unknown, var