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: Parameters
  ----------
  node
      Something previously returned by self.make_node.
  storage_map
      dict variable -> one-element-list where a computed
      value for this variable may be found.
  compute_map
      dict variable -> one-element-list where a boolean
      value will be found. The boolean indicates whether the
      variable's storage_map container contains a valid value (True)
      or if it has not been computed yet (False).
  no_recycling
      List of variables for which it is forbidden to reuse memory
      allocated by a previous call.
  impl
      Use 'py' if we want python execution.
  Notes
  -----
  If the thunk consults the storage_map on every call, it is safe
  for it to ignore the no_recycling argument, because elements of the
  no_recycling list will have a value of None in the storage map. If
  the thunk can potentially cache return values (like CLinker does),
  then it must not do so for variables in the no_recycling list.
docstr_tok: ["Parameters","--","--","--","--","--","node","Something","previously","returned","by","self.make_node",".","storage_map","dict","variable","-",">","one-element-list","where","a","computed","value","for","this","variable","may","be","found",".","compute_map","dict","variable","-",">","one-element-list","where","a","boolean","value","will","be","found",".","The","boolean","indicates","whether","the","variable","'s","storage_map","container","contains","a","valid","value","(","True",")","or","if","it","has","not","been","computed","yet","(","False",")",".","no_recycling","List","of","variables","for","which","it","is","forbidden","to","reuse","memory","allocated","by","a","previous","call",".","impl","Use","'py","'","if","we","want","python","execution",".","Notes","--","--","-","If","the","thunk","consults","the","storage_map","on","every","call",",","it","is","safe","for","it","to","ignore","the","no_recycling","argument",",","because","elements","of","the","no_recycling","list","will","have","a","value","of","None","in","the","storage","map",".","If","the","thunk","can","potentially","cache","return","values","(","like","CLinker","does",")",",","then","it","must","not","do","so","for","variables","in","the","no_recycling","list","."]
code: def make_thunk(
      self, node, storage_map, compute_map, no_recycling, impl=None
  ):
      """
  
      Parameters
      ----------
      node
          Something previously returned by self.make_node.
      storage_map
          dict variable -> one-element-list where a computed
          value for this variable may be found.
      compute_map
          dict variable -> one-element-list where a boolean
          value will be found. The boolean indicates whether the
          variable's storage_map container contains a valid value (True)
          or if it has not been computed yet (False).
      no_recycling
          List of variables for which it is forbidden to reuse memory
          allocated by a previous call.
      impl
          Use 'py' if we want python execution.
      Notes
      -----
      If the thunk consults the storage_map on every call, it is safe
      for it to ignore the no_recycling argument, because elements of the
      no_recycling list will have a value of None in the storage map. If
      the thunk can potentially cache return values (like CLinker does),
      then it must not do so for variables in the no_recycling list.
  
      """
  
      # Before building the thunk, validate that the inner graph is
      # coherent
      self.validate_inner_graph()
  
      # Setting up all my variables in what I believe is a more Cython
      # friendly form
  
      node_input_storage = [storage_map[r] for r in node.inputs]
      node_output_storage = [storage_map[r] for r in node.outputs]
      # If a shared variable is the result of a ViewOp it is a clear
      # indication that we need to copy that value after the perform of
      # scan is done
      slices = (
          self.n_mit_mot_outs
          + self.n_mit_sot
          + self.n_sit_sot
          + self.n_nit_sot
      )
  
      if theano.config.scan.allow_output_prealloc:
          # Go through the mitmots. Whenever a mitmot has a tap both as an
          # input and an output, wrap the input such that the corresponding
          # output variable becomes an update to be performed on it, possibly
          # inplace at the end of the functions's execution.
          wrapped_inputs = [
              In(x, borrow=False) for x in self.inputs[: self.n_seqs]
          ]
          new_outputs = [x for x in self.outputs]
          preallocated_mitmot_outs = []
          new_mit_mot_out_slices = copy.deepcopy(
              self.mit_mot_out_slices
          )
  
          input_idx = self.n_seqs
          for mitmot_idx in range(self.n_mit_mot):
              for inp_tap in self.tap_array[mitmot_idx]:
                  if inp_tap in self.mit_mot_out_slices[mitmot_idx]:
                      inp = self.inputs[input_idx]
  
                      # Figure out the index of the corresponding output
                      output_idx = sum(
                          [
                              len(m)
                              for m in self.mit_mot_out_slices[
                                  :mitmot_idx
                              ]
                          ]
                      )
                      output_idx += self.mit_mot_out_slices[
                          mitmot_idx
                      ].index(inp_tap)
  
                      # Make it so the input is automatically updated to the
                      # output value, possibly inplace, at the end of the
                      # function exectution. Also, since an update is
                      # defined, a default value must also be (this is
                      # verified by DebugMode). Use an array of size 0 but
                      # the right ndim and dtype (use a shape of 1 on
                      # broadcastable dimensions, 0 on the others).
                      default_shape = [
                          1 if _b else 0 for _b in inp.broadcastable
                      ]
                      default_val = inp.type.value_zeros(default_shape)
                      wrapped_inp = In(
                          variable=inp,
                          value=default_val,
                          update=self.outputs[output_idx],
                      )
                      wrapped_inputs.append(wrapped_inp)
                      preallocated_mitmot_outs.append(output_idx)
                      new_mit_mot_out_slices[mitmot_idx].remove(inp_tap)
                  else:
                      # Wrap the corresponding input as usual. Leave the
                      # output as-is.
                      wrapped_inputs.append(
                          In(self.inputs[input_idx], borrow=False)
                      )
                  input_idx += 1
  
          # Wrap the inputs not associated to mitmots and wrap the remaining
          # outputs
          wrapped_inputs += [
              In(x, borrow=False) for x in self.inputs[input_idx:]
          ]
          wrapped_outputs = [
              Out(x, borrow=True) for x in new_outputs[:slices]
          ]
          wrapped_outputs += new_outputs[slices:]
  
          # Remove now useless outputs from the output list (start from the
          # end to avoid altering the indices of the other outputs to be
          # deleted.
          preallocated_mitmot_outs.sort()
          for p in preallocated_mitmot_outs[::-1]:
              del wrapped_outputs[p]
  
          # Store the list of mitmot output taps that have been altered
          # so they can be preallocated
          self.mitmots_preallocated = [
              i in preallocated_mitmot_outs
              for i in range(self.n_mit_mot_outs)
          ]
  
          # Add an optimization to the compilation mode to attach a feature
          # to the function graph just before the inplace optimizations are
          # applied (inplace optimizations start at position 50 so the
          # optimization to attach the feature is registered at position 49.9
          # so that it runs before them). This feature will prevent mitsot,
          # sitsot and nitsot outputs from being computed inplace (to allow
          # their preallocation).
          mitsot_start = self.n_mit_mot_outs - len(
              preallocated_mitmot_outs
          )
          nitsot_end = (
              mitsot_start
              + self.n_mit_sot
              + self.n_sit_sot
              + self.n_nit_sot
          )
          feature = NoOutputFromInplace(mitsot_start, nitsot_end)
          opt = AddFeatureOptimizer(feature)
          compilation_mode = self.mode_instance.register((opt, 49.9))
  
      else:
          # Output preallocation is not activated. Mark every mitmot output
          # tap as not being preallocated
          self.mitmots_preallocated = [False] * self.n_mit_mot_outs
  
          wrapped_inputs = [In(x, borrow=True) for x in self.inputs]
          wrapped_outputs = [
              Out(x, borrow=False) for x in self.outputs[:slices]
          ]
          wrapped_outputs += self.outputs[slices:]
  
          compilation_mode = self.mode_instance
  
      profile = None
      if theano.config.profile or (
          isinstance(self.profile, (string_types, bool, integer_types))
          and self.profile
      ):
          if isinstance(self.profile, string_types):
              profile = ScanProfileStats(name=self.profile)
          else:
              profile = ScanProfileStats(name=self.name)
      elif self.profile:
          profile = self.profile
      # make_thunk can be called many times on the same op
      # we do not want to recompile the inner fct every time.
      if not getattr(self, "fn", None):
          self.fn = function(
              wrapped_inputs,
              wrapped_outputs,
              mode=compilation_mode,
              name=self.name,
              profile=profile,
              on_unused_input="ignore",
          )
  
      # Analyse the compile inner function to determine which inputs and
      # outputs are on the gpu and speed up some checks during the execution
      self.inps_is_tensor = [
          isinstance(out, theano.tensor.TensorVariable)
          for out in self.fn.maker.fgraph.inputs
      ]
      self.outs_is_tensor = [
          isinstance(out, theano.tensor.TensorVariable)
          for out in self.fn.maker.fgraph.outputs
      ]
  
      try:
          if impl == "py":
              raise theano.gof.cmodule.MissingGXX
          cython_mintaps = np.asarray(self.mintaps, dtype="int32")
          cython_tap_array_len = np.asarray(
              [len(x) for x in self.tap_array], dtype="int32"
          )
          if len(self.tap_array) == 0:
              d1 = 0
          else:
              d1 = np.max(cython_tap_array_len)
          d0 = len(self.tap_array)
          cython_tap_array = np.zeros((d0, d1), dtype="int32")
          for _d0 in xrange(d0):
              for _d1 in xrange(cython_tap_array_len[_d0]):
                  cython_tap_array[_d0, _d1] = self.tap_array[_d0][_d1]
          cython_mit_mot_out_nslices = np.asarray(
              [len(x) for x in self.mit_mot_out_slices], dtype="int32"
          )
          if len(self.mit_mot_out_slices) == 0:
              d1 = 0
          else:
              d1 = np.max(cython_mit_mot_out_nslices)
          d0 = len(self.mit_mot_out_slices)
          cython_mit_mot_out_slices = np.zeros((d0, d1), dtype="int32")
          for _d0 in xrange(d0):
              for _d1 in xrange(cython_mit_mot_out_nslices[_d0]):
                  cython_mit_mot_out_slices[
                      _d0, _d1
                  ] = self.mit_mot_out_slices[_d0][_d1]
  
          cython_vector_seqs = np.asarray(
              self.vector_seqs, dtype="int32"
          )
          cython_vector_outs = np.asarray(
              self.vector_outs, dtype="int32"
          )
          cython_mitmots_preallocated = np.asarray(
              self.mitmots_preallocated, dtype="int32"
          )
  
          cython_inps_is_tensor = np.asarray(
              self.inps_is_tensor, dtype="int32"
          )
          cython_outs_is_tensor = np.asarray(
              self.outs_is_tensor, dtype="int32"
          )
  
          if hasattr(self, "destroy_map"):
              cython_destroy_map = [
                  x in self.destroy_map
                  for x in xrange(len(node.outputs))
              ]
          else:
              cython_destroy_map = [
                  0 for x in xrange(len(node.outputs))
              ]
          cython_destroy_map = np.asarray(
              cython_destroy_map, dtype="int32"
          )
          from . import scan_perform_ext
  
          def p(node, args, outs):
              return scan_perform_ext.perform(
                  self.n_shared_outs,
                  self.n_mit_mot_outs,
                  self.n_seqs,
                  self.n_mit_mot,
                  self.n_mit_sot,
                  self.n_sit_sot,
                  self.n_nit_sot,
                  args[0],
                  self.as_while,
                  cython_mintaps,
                  cython_tap_array,
                  cython_tap_array_len,
                  cython_vector_seqs,
                  cython_vector_outs,
                  cython_mit_mot_out_slices,
                  cython_mit_mot_out_nslices,
                  cython_mitmots_preallocated,
                  cython_inps_is_tensor,
                  cython_outs_is_tensor,
                  self.fn.fn,
                  self.fn,
                  cython_destroy_map,
                  args,
                  outs,
                  self,
                  node,
              )
  
      except (ImportError, theano.gof.cmodule.MissingGXX):
          p = self.execute
      # default arguments are stored in the closure of `rval`
  
      # Big ugly hack since we can't get the real value of allow_gc
      # for the englobing function.
      allow_gc = config.allow_gc and not self.allow_gc
  
      def rval(
          p=p,
          i=node_input_storage,
          o=node_output_storage,
          n=node,
          allow_gc=allow_gc,
      ):
          r = p(n, [x[0] for x in i], o)
          for o in node.outputs:
              compute_map[o][0] = True
          if allow_gc:
              self.fn.free()
          return r
  
      rval.inputs = node_input_storage
      rval.outputs = node_output_storage
      rval.perform = p
      rval.lazy = False
      return rval
  
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 8 for calling the Scan obj's underlying member method code with example usage: obj.make_thunk(node, storage_map, compute_map, no_recycling, impl) and returns: rval, r, scan_perform_ext