class_docstr: Plain convolutional kernel as described in :cite:t:`vdw2017convgp`. Defines
  a GP :math:`f()` that is constructed from a sum of responses of individual patches
  in an image:
  
  .. math::
     f(x) = \sum_p x^{[p]}
  
  where :math:`x^{[p]}` is the :math:`p`'th patch in the image.
  
  The key reference is :cite:t:`vdw2017convgp`.
class_docstr_tok: ["Plain","convolutional","kernel","as","described","in",":","cite",":","t",":","`","vdw2017convgp","`",".","Defines","a","GP",":","math",":","`","f","(",")","`","that","is","constructed","from","a","sum","of","responses","of","individual","patches","in","an","image",":","..","math",":",":","f","(","x",")","=","\\sum_p","x^","{","[","p","]","}","where",":","math",":","`","x^","{","[","p","]","}","`","is","the",":","math",":","`","p","`","'th","patch","in","the","image",".","The","key","reference","is",":","cite",":","t",":","`","vdw2017convgp","`","."]
code: def K(
      self, X: TensorType, X2: Optional[TensorType] = None
  ) -> tf.Tensor:
      Xp = cs(self.get_patches(X), "[batch..., N, P, S]")
      W2 = cs(self.weights[:, None] * self.weights[None, :], "[P, P]")
  
      rank = tf.rank(Xp) - 3
      batch = tf.shape(Xp)[:-3]
      N = tf.shape(Xp)[-3]
      P = tf.shape(Xp)[-2]
      S = tf.shape(Xp)[-1]
      ones = tf.ones((rank,), dtype=tf.int32)
  
      if X2 is None:
          Xp = cs(
              tf.reshape(Xp, tf.concat([batch, [N * P, S]], 0)),
              "[batch..., N_x_P, S]",
          )
          bigK = cs(self.base_kernel.K(Xp), "[batch..., N_x_P, N_x_P]")
          bigK = cs(
              tf.reshape(bigK, tf.concat([batch, [N, P, N, P]], 0)),
              "[batch..., N, P, N, P]",
          )
          W2 = cs(
              tf.reshape(W2, tf.concat([ones, [1, P, 1, P]], 0)),
              "[..., 1, P, 1, P]",
          )
          W2bigK = cs(bigK * W2, "[batch..., N, P, N, P]")
          return cs(
              tf.reduce_sum(W2bigK, [rank + 1, rank + 3])
              / self.num_patches**2.0,
              "[batch..., N, N]",
          )
  
      else:
          Xp2 = (
              Xp
              if X2 is None
              else cs(self.get_patches(X2), "[batch2..., N2, P, S]")
          )
          rank2 = tf.rank(Xp2) - 3
          ones2 = tf.ones((rank2,), dtype=tf.int32)
          bigK = cs(
              self.base_kernel.K(Xp, Xp2),
              "[batch..., N, P, batch2..., N2, P]",
          )
          W2 = cs(
              tf.reshape(
                  W2, tf.concat([ones, [1, P], ones2, [1, P]], 0)
              ),
              "[..., 1, P, ..., 1, P]",
          )
          W2bigK = cs(bigK * W2, "[batch..., N, P, batch2..., N2, P]")
          return cs(
              tf.reduce_sum(W2bigK, [rank + 1, rank + rank2 + 3])
              / self.num_patches**2.0,
              "[batch..., N, batch2..., N2]",
          )
  
file_path: GPflow/gpflow/kernels/convolutional.py
imports: from typing import Optional, Sequence, cast
  import numpy
  import tensorflow
  from check_shapes import check_shape
  from check_shapes import check_shapes, inherit_check_shapes
  from ..base import Parameter, TensorType
  from ..config import default_float
  from ..utilities import to_default_float
  from .base import Kernel
label_desc: Use image node_id 3 for calling the Convolutional obj's underlying member method code with example usage: obj.K(X, X2) and returns: cs, cs