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 infer_shape(self, node, input_shapes):
      # input_shapes correspond to the shapes of node.inputs
      for inp, inp_shp in izip(node.inputs, input_shapes):
          assert inp_shp is None or len(inp_shp) == inp.type.ndim
  
      # Here we build 2 variables;
      # - A list `inner_ins_shapes`, such that inner_ins_shapes[i] is the
      #   shape of self.inputs[i]
      # - A dictionary `out_equivalent` containing, for every inner input,
      #   an equivalent variable computed from the outer inputs.
      #   NOTE : For non-sequences, this equivalence is trivial. For
      #   sequences and recurrent states, there is no direct equivalence
      #   between outer and inner inputs. However, because every iteration
      #   of the Scan needs to give the same output shapes, we can give an
      #   equivalence between these inner inputs and the subelements of the
      #   corresponding outer inputs that the Scan would use as input for
      #   any given iteration. For simplicity, we use iteration 0.
      inner_ins_shapes = []
      out_equivalent = OrderedDict()
  
      # The two following blocks are commented as it cause in some
      # cases extra scans in the graph. See gh-XXX for the
      # investigation.
  
      # We skip the first outer input as it is the total or current number
      # of iterations.
      # sequences
      seqs_shape = [x[1:] for x in input_shapes[1 : 1 + self.n_seqs]]
      # We disable extra infer_shape for now. See gh-3765.
      extra_infer_shape = False
  
      if extra_infer_shape:
          inner_seqs = self.inputs[: self.n_seqs]
          outer_seqs = node.inputs[1 : 1 + self.n_seqs]
          for in_s, out_s in izip(inner_seqs, outer_seqs):
              out_equivalent[in_s] = out_s[0]
  
          # mit_mot, mit_sot, sit_sot
          outer_inp_idx = 1 + self.n_seqs
          inner_inp_idx = self.n_seqs
      else:
          outer_inp_idx = 0
      n_outs = self.n_mit_mot + self.n_mit_sot + self.n_sit_sot
      outs_shape = []
      for idx in xrange(n_outs):
          mintap = abs(min(self.tap_array[idx]))
          for k in self.tap_array[idx]:
              outs_shape += [input_shapes[idx + self.n_seqs + 1][1:]]
              if extra_infer_shape:
                  corresponding_tap = node.inputs[outer_inp_idx][
                      mintap + k
                  ]
                  out_equivalent[
                      self.inputs[inner_inp_idx]
                  ] = corresponding_tap
                  inner_inp_idx += 1
          outer_inp_idx += 1
  
      # shared_outs
      offset = 1 + self.n_seqs + n_outs
      for idx in xrange(self.n_shared_outs):
          outs_shape += [input_shapes[idx + offset]]
  
      # non_sequences
      offset += self.n_nit_sot + self.n_shared_outs
      inner_ins_shapes = seqs_shape + outs_shape + input_shapes[offset:]
      assert len(inner_ins_shapes) == len(self.inputs)
  
      # Non-sequences have a direct equivalent from self.inputs in
      # node.inputs
      inner_non_sequences = self.inputs[
          len(seqs_shape) + len(outs_shape) :
      ]
      for in_ns, out_ns in izip(
          inner_non_sequences, node.inputs[offset:]
      ):
          out_equivalent[in_ns] = out_ns
  
      if self.as_while:
          self_outs = self.outputs[:-1]
      else:
          self_outs = self.outputs
      outs_shape = scan_utils.infer_shape(
          outs=self_outs,
          inputs=self.inputs,
          input_shapes=inner_ins_shapes,
      )
      # Will be used to check if outs_shape can be expressed without using
      # variables in self.inputs.
      # The shapes of node.inputs are valid.
      validator = scan_utils.Validator(
          valid=input_shapes,
          invalid=self.inputs,
          valid_equivalent=out_equivalent,
      )
  
      offset = 1 + self.n_seqs
      scan_outs = [x for x in input_shapes[offset : offset + n_outs]]
      offset += n_outs
      outs_shape_n = (
          self.n_mit_mot_outs + self.n_mit_sot + self.n_sit_sot
      )
      for x in xrange(self.n_nit_sot):
          out_shape_x = outs_shape[outs_shape_n + x]
          if out_shape_x is None:
              # This output is not a tensor, and has no shape
              scan_outs.append(None)
          else:
              # We need to make sure that we can compute the shapes from
              # node.inputs, and constants, without using the variables
              # in the inner function.
              r = node.outputs[n_outs + x]
              assert r.ndim == 1 + len(out_shape_x)
              shp = [node.inputs[offset + self.n_shared_outs + x]]
              for i, shp_i in izip(xrange(1, r.ndim), out_shape_x):
                  # Validate shp_i. v_shape_i is either None (if invalid),
                  # or a (variable, Boolean) tuple. The Boolean indicates
                  # whether variable is shp_i (if True), or an valid
                  # equivalent (if False). Here, we only need the variable.
                  v_shp_i = validator.check(shp_i)
                  if v_shp_i is None:
                      if (
                          hasattr(r, "broadcastable")
                          and r.broadcastable[i]
                      ):
                          shp.append(1)
                      else:
                          shp.append(Shape_i(i)(r))
                  else:
                      # It can (or at least, an equivalent variable can)
                      shp.append(v_shp_i[0])
              scan_outs.append(tuple(shp))
  
      scan_outs += [
          x for x in input_shapes[offset : offset + self.n_shared_outs]
      ]
      # if we are dealing with a repeat-until, then we do not know the
      # leading dimension so we replace it for every entry with Shape_i
      if self.as_while:
          scan_outs_init = scan_outs
          scan_outs = []
          for o, x in izip(node.outputs, scan_outs_init):
              if x is None:
                  scan_outs.append(None)
              else:
                  scan_outs.append((Shape_i(0)(o),) + x[1:])
      return scan_outs
  
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 36 for calling the Scan obj's underlying member method code with example usage: obj.infer_shape(node, input_shapes) and returns: scan_outs