class_docstr: Marginal distribution of a Multitask GP at finitely many points.
code: def _variance(self, index_points=None):
      index_points = self._get_index_points(index_points)
      kernel_matrix = self.kernel.matrix_over_all_tasks(
          index_points, index_points
      )
      observation_noise_variance = None
      if self.observation_noise_variance is not None:
          observation_noise_variance = tf.convert_to_tensor(
              self.observation_noise_variance
          )
  
      # We can add the observation noise to each block.
      if isinstance(self.kernel, multitask_kernel.Independent):
          single_task_variance = kernel_matrix.operators[0].diag_part()
          if observation_noise_variance is not None:
              single_task_variance = (
                  single_task_variance
                  + observation_noise_variance[..., tf.newaxis]
              )
          # Each task has the same variance, so shape this in to an `[..., e, t]`
          # shaped tensor and broadcast to batch shape
          variance = tf.stack(
              [single_task_variance] * self.kernel.num_tasks, axis=-1
          )
          # Finally broadcast with batch shape.
          batch_shape = self._batch_shape_tensor(
              index_points=index_points
          )
          event_shape = self._event_shape_tensor(
              index_points=index_points
          )
  
          variance = tf.broadcast_to(
              variance, ps.concat([batch_shape, event_shape], axis=0)
          )
          return variance
  
      # If `kernel_matrix` has structure, `diag_part` will try to take advantage
      # of that structure. In the case of a `Separable` kernel, `diag_part` will
      # efficiently compute the diagonal of a kronecker product.
      variance = kernel_matrix.diag_part()
      if observation_noise_variance is not None:
          variance = (
              variance + observation_noise_variance[..., tf.newaxis]
          )
  
      variance = _unvec(variance, [-1, self.kernel.num_tasks])
  
      # Finally broadcast with batch shape.
      batch_shape = self._batch_shape_tensor(index_points=index_points)
      event_shape = self._event_shape_tensor(index_points=index_points)
  
      variance = tf.broadcast_to(
          variance, ps.concat([batch_shape, event_shape], axis=0)
      )
      return variance
  
imports: import functools
  import tensorflow.compat.v2
  from tensorflow_probability.python.distributions import cholesky_util
  from tensorflow_probability.python.distributions import distribution
  from tensorflow_probability.python.distributions import mvn_linear_operator
  from tensorflow_probability.python.distributions.internal import stochastic_process_util
  from tensorflow_probability.python.experimental.linalg import linear_operator_unitary
  from tensorflow_probability.python.experimental.psd_kernels import multitask_kernel
  from tensorflow_probability.python.internal import batch_shape_lib
  from tensorflow_probability.python.internal import distribution_util
  from tensorflow_probability.python.internal import dtype_util
  from tensorflow_probability.python.internal import nest_util
  from tensorflow_probability.python.internal import parameter_properties
  from tensorflow_probability.python.internal import prefer_static
  from tensorflow_probability.python.internal import reparameterization
  from tensorflow_probability.python.internal import tensor_util
  from tensorflow_probability.python.internal import tensorshape_util
  from tensorflow.python.util import nest
label_desc: Use image node_id 17 for calling the MultiTaskGaussianProcess obj's underlying member method code with example usage: obj._variance(index_points) and returns: variance, variance