class_docstr: Common base class for SGPR and GPRFITC that provides the common __init__
  and upper_bound() methods.
docstr: Upper bound for the sparse GP regression marginal likelihood.  Note that
  the same inducing points are used for calculating the upper bound, as are
  used for computing the likelihood approximation. This may not lead to the
  best upper bound. The upper bound can be tightened by optimising Z, just
  like the lower bound. This is especially important in FITC, as FITC is
  known to produce poor inducing point locations. An optimisable upper bound
  can be found in https://github.com/markvdw/gp_upper.
  
  The key reference is :cite:t:`titsias_2014`.
  
  The key quantity, the trace term, can be computed via
  
  >>> _, v = conditionals.conditional(X, model.inducing_variable.Z, model.kernel,
  ...                                 np.zeros((model.inducing_variable.num_inducing, 1)))
  
  which computes each individual element of the trace term.
docstr_tok: ["Upper","bound","for","the","sparse","GP","regression","marginal","likelihood",".","Note","that","the","same","inducing","points","are","used","for","calculating","the","upper","bound",",","as","are","used","for","computing","the","likelihood","approximation",".","This","may","not","lead","to","the","best","upper","bound",".","The","upper","bound","can","be","tightened","by","optimising","Z",",","just","like","the","lower","bound",".","This","is","especially","important","in","FITC",",","as","FITC","is","known","to","produce","poor","inducing","point","locations",".","An","optimisable","upper","bound","can","be","found","in","https",":","\/\/github.com\/markvdw\/gp_upper",".","The","key","reference","is",":","cite",":","t",":","`","titsias_2014","`",".","The","key","quantity",",","the","trace","term",",","can","be","computed","via",">",">",">","_",",","v","=","conditionals.conditional","(","X",",","model.inducing_variable.Z",",","model.kernel",",","...","np.zeros","(","(","model.inducing_variable.num_inducing",",","1",")",")",")","which","computes","each","individual","element","of","the","trace","term","."]
code: def upper_bound(self) -> tf.Tensor:
      """
      Upper bound for the sparse GP regression marginal likelihood.  Note that
      the same inducing points are used for calculating the upper bound, as are
      used for computing the likelihood approximation. This may not lead to the
      best upper bound. The upper bound can be tightened by optimising Z, just
      like the lower bound. This is especially important in FITC, as FITC is
      known to produce poor inducing point locations. An optimisable upper bound
      can be found in https://github.com/markvdw/gp_upper.
  
      The key reference is :cite:t:`titsias_2014`.
  
      The key quantity, the trace term, can be computed via
  
      >>> _, v = conditionals.conditional(X, model.inducing_variable.Z, model.kernel,
      ...                                 np.zeros((model.inducing_variable.num_inducing, 1)))
  
      which computes each individual element of the trace term.
      """
      X_data, Y_data = self.data
  
      sigma_sq = tf.squeeze(
          self.likelihood.variance_at(X_data), axis=-1
      )  # [N]
      sigma = tf.sqrt(sigma_sq)  # [N]
  
      Kdiag = self.kernel(X_data, full_cov=False)
      kuu = Kuu(
          self.inducing_variable, self.kernel, jitter=default_jitter()
      )
      kuf = Kuf(self.inducing_variable, self.kernel, X_data)
  
      I = tf.eye(tf.shape(kuu)[0], dtype=default_float())
  
      L = tf.linalg.cholesky(kuu)
      A = tf.linalg.triangular_solve(L, kuf, lower=True)
  
      A_sigma = tf.linalg.triangular_solve(L, kuf / sigma, lower=True)
      AAT_sigma = tf.linalg.matmul(A_sigma, A_sigma, transpose_b=True)
      B = I + AAT_sigma
      LB = tf.linalg.cholesky(B)
  
      # Using the Trace bound, from Titsias' presentation
      c = tf.reduce_sum(Kdiag) - tf.reduce_sum(tf.square(A))
  
      # Alternative bound on max eigenval:
      cn_var = sigma_sq + c
      cn_std = tf.sqrt(cn_var)
  
      const = -0.5 * tf.reduce_sum(tf.math.log(2 * np.pi * sigma_sq))
      logdet = -tf.reduce_sum(tf.math.log(tf.linalg.diag_part(LB)))
  
      A_cn = tf.linalg.triangular_solve(L, kuf / cn_std, lower=True)
      AAT_cn = tf.linalg.matmul(A_cn, A_cn, transpose_b=True)
  
      err = Y_data - self.mean_function(X_data)
      LC = tf.linalg.cholesky(I + AAT_cn)
      v = tf.linalg.triangular_solve(
          LC, tf.linalg.matmul(A_cn, err / cn_std[:, None]), lower=True
      )
      quad = -0.5 * tf.reduce_sum(
          tf.square(err / cn_std[:, None])
      ) + 0.5 * tf.reduce_sum(tf.square(v))
  
      return const + logdet + quad
  
imports: from typing import NamedTuple, Optional, Tuple
  import numpy
  import tensorflow
  from check_shapes import check_shapes, inherit_check_shapes
  from ..None import posteriors
  from ..base import InputData, MeanAndVariance, RegressionData, TensorData
  from ..config import default_float, default_jitter
  from ..covariances.dispatch import Kuf, Kuu
  from ..inducing_variables import InducingPoints
  from ..kernels import Kernel
  from ..likelihoods import Gaussian
  from ..mean_functions import MeanFunction
  from ..utilities import add_noise_cov, assert_params_false, to_default_float
  from .model import GPModel
  from .training_mixins import InternalDataTrainingLossMixin
  from .util import InducingPointsLike, data_input_to_tensor, inducingpoint_wrapper
label_desc: Use image node_id 2 for calling the SGPRBase_deprecated obj's underlying member method code with example usage: obj.upper_bound() and returns: unknown