class_docstr: The ChangePoints kernel defines a fixed number of change-points along a 1d
  input space where different kernels govern different parts of the space.
  
  The kernel is by multiplication and addition of the base kernels with
  sigmoid functions (σ). A single change-point kernel is defined as::
  
      K₁(x, x') * (1 - σ(x)) * (1 - σ(x')) + K₂(x, x') * σ(x) * σ(x')
  
  where K₁ is deactivated around the change-point and K₂ is activated. The
  single change-point version can be found in :cite:t:`lloyd2014`. Each sigmoid
  is a logistic function defined as::
  
      σ(x) = 1 / (1 + exp{-s(x - x₀)})
  
  parameterized by location "x₀" and steepness "s".
  
  The key reference is :cite:t:`lloyd2014`.
code: def K(
      self, X: tf.Tensor, X2: Optional[tf.Tensor] = None
  ) -> tf.Tensor:
      cs(
          X,
          "[batch..., N, 1]  # The `ChangePoints` kernel requires a 1D input space.",
      )
  
      rank = tf.rank(X) - 2
      batch = tf.shape(X)[:-2]
      N = tf.shape(X)[-2]
      Ncp = tf.shape(self.locations)[0]
      sig_X = cs(self._sigmoids(X), "[batch..., N, 1, Ncp]")
  
      if X2 is None:
          rank2 = 0
          batch2 = tf.constant([], dtype=tf.int32)
          N2 = N
          sig_X2 = sig_X
          sig_X = cs(
              tf.reshape(sig_X, tf.concat([batch, [N, 1, Ncp]], 0)),
              "[batch..., N, 1, Ncp]",
          )
          sig_X2 = cs(
              tf.reshape(sig_X2, tf.concat([batch, [1, N, Ncp]], 0)),
              "[batch..., 1, N, Ncp]",
          )
      else:
          rank2 = tf.rank(X2) - 2
          batch2 = tf.shape(X2)[:-2]
          N2 = tf.shape(X2)[-2]
  
          sig_X2 = cs(self._sigmoids(X2), "[batch2..., N2, 1, Ncp]")
  
          ones = tf.ones((rank,), dtype=tf.int32)
          ones2 = tf.ones((rank2,), dtype=tf.int32)
          sig_X = cs(
              tf.reshape(
                  sig_X, tf.concat([batch, [N], ones2, [1, Ncp]], 0)
              ),
              "[batch..., N, ..., 1, Ncp]",
          )
          sig_X2 = cs(
              tf.reshape(
                  sig_X2, tf.concat([ones, [1], batch2, [N2, Ncp]], 0)
              ),
              "[..., 1, batch2..., N2, Ncp]",
          )
  
      # `starters` are the sigmoids going from 0 -> 1, whilst `stoppers` go
      # from 1 -> 0.
      starters = cs(sig_X * sig_X2, "[batch..., N, batch2..., N2, Ncp]")
      stoppers = cs(
          (1 - sig_X) * (1 - sig_X2),
          "[batch..., N, batch2..., N2, Ncp]",
      )
  
      # prepend `starters` with ones and append ones to `stoppers` since the
      # first kernel has no start and the last kernel has no end
      ones = tf.ones(
          tf.concat([batch, [N], batch2, [N2, 1]], 0), dtype=X.dtype
      )
      starters = cs(
          tf.concat([ones, starters], axis=-1),
          "[batch..., N, batch2..., N2, Nkern]",
      )
      stoppers = cs(
          tf.concat([stoppers, ones], axis=-1),
          "[batch..., N, batch2..., N2, Nkern]",
      )
  
      # now combine with the underlying kernels
      kernel_stack = cs(
          tf.stack([k(X, X2) for k in self.kernels], axis=-1),
          "[batch..., N, batch2..., N2, Nkern]",
      )
      return tf.reduce_sum(kernel_stack * starters * stoppers, axis=-1)
  
file_path: GPflow/gpflow/kernels/changepoints.py
imports: from typing import Optional, Sequence
  import tensorflow
  from check_shapes import check_shape
  from check_shapes import check_shapes, inherit_check_shapes
  from ..base import Parameter, TensorType
  from ..utilities import positive
  from .base import Combination, Kernel
label_desc: Use image node_id 3 for calling the ChangePoints obj's underlying member method code with example usage: obj.K(X, X2) and returns: tf