class_docstr: This Op serves a dual purpose: it can implement a vanilla 2D convolution
  (as taught in any signal processing class) or implement the
  convolutional layers found in Convolutional Neural Networks.
  
  In this setting, a set of 3D images is convolved with a set of 3D kernels,
  with the particularity that their leading dimensions are of equal length.
  Vanilla 2D convolution is treated as a special case of this.
  
  The input parameter represents a mini-batch of multiple images. Its shape is:
      batch size x num. input feature maps x image height x image width
  
  The kernel parameter represents a set of 3D kernels. Its shape is:
      number of filters x num. input images x filter height x filter width
  
  The output of ConvOp is a 4D tensor, generated as follows:
      output[b,k,:,:] = \sum_i input[b,i,:,:] * filter[k,i,:,:] \forall b,k
  where b is the mini-batch index, k the filter index and * is the
  convolution operator.
  
  The constructor initializes a ConvOp with given output_mode (full/valid).
  All other parameters are optional and are only used to generate more
  optimized c code, or to enable graph optimizers to optimally replace the
  ConvOp.
  
  NOTES ON OPTIMIZATION:
  There are two types of optimization. The first is the selection of the
  fastest algo when bsize and nkern are provided with imshp and kshp.
  By default we try to select the fastest version. You can specify it
  with the unroll_batch, unroll_kern, and unroll_patch parameter.
  
  The second type of optimization is hardcoding some dimensions into the
  code when all shape are know.
  This make a significant difference for the 'full' output_mode.
  
  Sometimes, the fastest implementation on x86-64 uses
  {unroll_batch=4, unroll_kern=4, unroll_patch=False}
  with all other shape parameters being provided.
  
  For optimizing other architectures, see:
  Kazushige Goto and Robert A. Van De Geijn, Anatomy of High-Performance
  Matrix Multiplication, (mr x nr). ACM Transactions on Mathematical
  Software, May 2008.
  Figure 12: (mr x nr). For x86 use 2x4, itanium 8x8, etc.
  
  Parameters
  ----------
  output_mode : {'valid', 'full'}
      'valid' gives an output smaller then the image.
      'full' gives an output bigger then the image.
       See 'border_mode' in conv2d's doc.
  
  Optional parameters: (will generate more optimal c code)
  
  imshp : tuple of len 2 or 3: 2 for 2d image, 3 for a stack of 2d images.
      Stacksize, nb image row, nb image col.
  kshp : tuple of len 2
      Nb kernel row, nb kernel col.
  nkern : int
      The number of kernel.
  bsize : int
      The size of the minibatch.
  dx : int
      Patch stride rows.
  dy : int
      Patch stride cols
  
  Params which select the version of code used:
  
  unroll_patch : bool
      Use a version of c_code that unroll the patch loop that don't
      request all shape information to work, but if all shape information
      are present, will use it to hardcode the value in the code for
      faster code.
  unroll_batch : int
      Use a version of c_code that unroll the batch (by unroll_batch)
      and the nkern (by unroll_kern) loop. The size must by a multiple
      of bsize or nkern respectively.
  unroll_kern : int
      Use a version of c_code that unroll the batch
      (by unroll_batch) and the nkern(by unroll_kern) loop. The size
      must by a multiple of bsize or nkern respectively.
  verbose : int
      Passed to GpuConv.
  version: int or str
      Passed to GpuConv, if version='no_fft', fft
      optimization will be desactivated at the op level.
  direction_hint: {'forward', 'bprop weights', 'bprop inputs'}
      Passed to GpuConv, used by graph optimizers to aid algorithm choice.
  
  The 3 following parameters are used internally when we generate
  the gradient when dx!=1 or dy!=1.
  
  imshp_logical
      Default None. None value is equivalent to imshp value.
      When imshp_logical != imshp, it tell we need to insert 0 in
      the image before we do the convolution. For example, when dx==dy==2
      and the image is [[1, 2], [3, 4]], we should make as if the image
      was [[1, 0, 2, 0], [0, 0, 0, 0], [3, 0, 4, 0], [0, 0, 0, 0]].
      Our python code insert the zero, but the c code optimize it.
      imshp_logical != imshp when taking the grad again the weights or
      the image when the output_mode is full and `dx != 1` or `dy != 1`.
  kshp_logical
      Idem but for kshp and used for the grad again the
      weights when the output_mode is valid and `dx != 1` or `dy != 1`.
  kshp_logical_top_aligned
      Used in the same case. Default to True.
      Set to False in the grad again the weight when the
      output_mode is full.
code: def __init__(
      self,
      imshp=None,
      kshp=None,
      nkern=None,
      bsize=None,
      dx=1,
      dy=1,
      output_mode="valid",
      unroll_batch=None,
      unroll_kern=None,
      unroll_patch=None,
      imshp_logical=None,
      kshp_logical=None,
      kshp_logical_top_aligned=True,
      verbose=0,
      version=-1,
      direction_hint="forward",
      openmp=None,
  ):
      # Deactivate fft_optimization at the op level if specified
      if version == "no_fft":
          self.fft_opt = False
          version = -1
      else:
          self.fft_opt = True
  
      # Expand unknown image / kernel shapes into tuples of Nones
      if imshp is None:
          imshp = (None, None, None)
      else:
          imshp = tuple(imshp)
      if kshp is None:
          kshp = (None, None)
      else:
          kshp = tuple(kshp)
  
      # Check imshp and kshp dimensionality
      if len(imshp) == 2:
          imshp = (1,) + imshp
      elif len(imshp) != 3:
          raise ValueError(
              "len(imshp) must be 2 or 3, got %d" % len(imshp)
          )
      if len(kshp) != 2:
          raise ValueError("len(kshp) must be 2, got %d" % len(kshp))
  
      # We must continue to consider None as 1 for backward compatibility.
      if dx is None:
          dx = 1
      if dy is None:
          dy = 1
  
      if int(dx) != dx:
          raise TypeError("ConvOp.__init__ param dx must be an int", dx)
      dx = int(dx)
  
      if int(dy) != dy:
          raise TypeError("ConvOp.__init__ param dy must be an int", dy)
      dy = int(dy)
  
      all_shape = self.has_all_shape(imshp, kshp, nkern, bsize)
      if (unroll_batch or unroll_kern) and not all_shape:
          raise Exception(
              "In ConvOp, when using unroll_batch and"
              " unroll_nkern, all shape are needed"
          )
  
      # Init the openmp attribute
      super(ConvOp, self).__init__(openmp=openmp)
      if not all_shape or self.openmp:
          # Only this version is parallelized
          unroll_patch = True
  
      self.imshp = imshp
      self.kshp = kshp
      self.nkern = nkern
      self.bsize = bsize
      self.dx = dx
      self.dy = dy
      self.verbose = verbose
      self.version = version
      self.direction_hint = direction_hint
  
      # a triple
      if imshp_logical is None:
          self.imshp_logical = self.imshp
      else:
          imshp_logical = tuple(imshp_logical)
          if len(imshp_logical) != 3:
              raise ValueError(
                  "len(imshp_logical) must be 3, got %d"
                  % len(imshp_logical)
              )
          self.imshp_logical = imshp_logical
  
      # a pair
      if kshp_logical is None:
          self.kshp_logical = self.kshp
      else:
          kshp_logical = tuple(kshp_logical)
          if len(kshp_logical) != 2:
              raise ValueError(
                  "len(kshp_logical) must be 2, got %d"
                  % len(kshp_logical)
              )
          self.kshp_logical = kshp_logical
  
      # a bool
      self.kshp_logical_top_aligned = kshp_logical_top_aligned
  
      self.unroll_batch = unroll_batch
      self.unroll_kern = unroll_kern
      self.unroll_patch = unroll_patch
  
      if self.unroll_batch and not self.unroll_kern:
          self.unroll_kern = 1
      if self.unroll_kern and not self.unroll_batch:
          self.unroll_batch = 1
  
      # downcast unroll_batch if not a divisor of batch size
      if (
          self.unroll_batch is not None
          and self.unroll_batch > 0
          and self.bsize % self.unroll_batch != 0
      ):
          if self.bsize <= self.unroll_batch:
              self.unroll_batch = self.bsize
          else:
              # find the maximum value under unroll_batch that would work
              new = self.unroll_batch
              assert new >= 1
              while self.bsize % new != 0:
                  new -= 1
  
              warnstr = (
                  "OPTIMISATION WARNING: in ConvOp.__init__() "
                  "unroll_batch(%i) must be 0 or a divisor of"
                  " bsize(%i). We revert it to %i. This"
                  " won't change the result, but may make it slower."
              )
              _logger.warn(warnstr, self.unroll_batch, self.bsize, new)
  
              self.unroll_batch = new
  
      # downcast unroll_kern if not a divisor of nb of kernel
      if (
          self.unroll_kern is not None
          and self.unroll_kern > 0
          and self.nkern % self.unroll_kern != 0
      ):
          if self.nkern <= self.unroll_kern:
              self.unroll_kern = self.nkern
          else:
              # find the maximum value under unroll_kern that would work
              new = self.unroll_kern
              assert new >= 1
              while self.nkern % new != 0:
                  new -= 1
  
              warnstr = (
                  "OPTIMISATION WARNING: in ConvOp.__init__()"
                  " unroll_kern(%i) should be 0 or a divisor of"
                  " nkern(%i). We revert it to %i. This"
                  " won't change the result, but may make it slower."
              )
              _logger.warn(warnstr, self.unroll_kern, self.nkern, new)
              self.unroll_kern = new
  
      self.outshp = get_conv_output_shape(
          (None,) + self.imshp_logical,
          (
              None,
              None,
          )
          + self.kshp_logical,
          output_mode,
          (dx, dy),
      )[2:]
      self.fulloutshp = get_conv_output_shape(
          (None,) + self.imshp_logical,
          (
              None,
              None,
          )
          + self.kshp_logical,
          output_mode,
          (1, 1),
      )[2:]
  
      self.out_mode = output_mode
  
      if self.out_mode not in ["valid", "full"]:
          raise Exception(
              "Mode %s not implemented" % str(self.out_mode)
          )
  
      if any((shp is not None) and (shp <= 0) for shp in self.outshp):
          raise Exception(
              "Bad size for the output shape. Verify that [post-"
              "supersampling] input shape (%s) and kern"
              " shape(%s) are ok. (Hint: kerns must fit inside"
              " image in valid mode)"
              % (self.imshp_logical, self.kshp_logical)
          )
  
      if (
          self.unroll_kern is None
          and self.unroll_batch is None
          and self.unroll_patch is None
      ):
          # no version specified. Find the faster we have
          if self.bsize is None and self.nkern is None:
              self.unroll_patch = True
          elif self.bsize is not None and self.nkern is not None:
              bsize = self.bsize
              nkern = self.nkern
              mode_idx = 0
              if self.out_mode != "valid":
                  mode_idx = 1
              if self.has_all_shape(self.imshp, self.kshp):
                  time_unroll_patch = self.speed_unroll_patch_shape[
                      mode_idx
                  ]
              else:
                  time_unroll_patch = self.speed_unroll_patch_noshape[
                      mode_idx
                  ]
              time_unroll_batch_kern = 9999999
              for i in xrange(len(self.speed_unroll_batch_kern)):
                  if (
                      bsize % self.speed_unroll_batch_kern[i][0] == 0
                      and nkern % self.speed_unroll_batch_kern[i][1]
                      == 0
                  ):
                      if (
                          self.speed_unroll_batch_kern[i][2 + mode_idx]
                          < time_unroll_batch_kern
                      ):
                          time_unroll_batch_kern = (
                              self.speed_unroll_batch_kern[i][
                                  2 + mode_idx
                              ]
                          )
                          time_unroll_batch_kern_idx = i
              if time_unroll_patch < time_unroll_batch_kern:
                  self.unroll_patch = True
              else:
                  self.unroll_batch = self.speed_unroll_batch_kern[
                      time_unroll_batch_kern_idx
                  ][0]
                  self.unroll_kern = self.speed_unroll_batch_kern[
                      time_unroll_batch_kern_idx
                  ][1]
                  self.unroll_patch = False
  
          _logger.debug(
              "AUTO FIND VERSION OF C_CODE OF CONV OP "
              "%s %s %s %s %s %s %s",
              self.unroll_batch,
              self.unroll_kern,
              self.unroll_patch,
              self.bsize,
              self.nkern,
              time_unroll_patch,
              time_unroll_batch_kern,
          )
  
      self._rehash()
  
imports: from __future__ import absolute_import, print_function, division
  import logging
  import numpy
  from six.moves import xrange
  import warnings
  import theano
  from theano import OpenMPOp
  from theano.tensor import as_tensor_variable, blas, get_scalar_constant_value, patternbroadcast, NotScalarConstantError
  from theano.gof import Apply
  from theano.tensor.nnet.abstract_conv import get_conv_output_shape, get_conv_shape_1axis
label_desc: Use image node_id 3 to create a new ConvOp object from inherited base classes: OpenMPOp with example: obj = ConvOp(imshp, kshp, nkern, bsize, dx, dy, output_mode, unroll_batch, unroll_kern, unroll_patch, imshp_logical, kshp_logical, kshp_logical_top_aligned, verbose, version, direction_hint, openmp)