docstr: Construct a tensorflow function to compute the bound on the marginal
  likelihood.
docstr_tok: ["Construct","a","tensorflow","function","to","compute","the","bound","on","the","marginal","likelihood","."]
code: def elbo(self) -> tf.Tensor:
      """
      Construct a tensorflow function to compute the bound on the marginal
      likelihood.
      """
      Y_data = self.data
  
      pX = DiagonalGaussian(self.X_data_mean, self.X_data_var)
  
      num_inducing = self.inducing_variable.num_inducing
      psi0 = tf.reduce_sum(expectation(pX, self.kernel))
      psi1 = expectation(pX, (self.kernel, self.inducing_variable))
      psi2 = tf.reduce_sum(
          expectation(
              pX,
              (self.kernel, self.inducing_variable),
              (self.kernel, self.inducing_variable),
          ),
          axis=0,
      )
      cov_uu = covariances.Kuu(
          self.inducing_variable, self.kernel, jitter=default_jitter()
      )
      L = tf.linalg.cholesky(cov_uu)
      sigma2 = self.likelihood.variance
  
      # Compute intermediate matrices
      A = tf.linalg.triangular_solve(L, tf.transpose(psi1), lower=True)
      tmp = tf.linalg.triangular_solve(L, psi2, lower=True)
      AAT = (
          tf.linalg.triangular_solve(L, tf.transpose(tmp), lower=True)
          / sigma2
      )
      B = AAT + tf.eye(num_inducing, dtype=default_float())
      LB = tf.linalg.cholesky(B)
      log_det_B = 2.0 * tf.reduce_sum(
          tf.math.log(tf.linalg.diag_part(LB))
      )
      c = (
          tf.linalg.triangular_solve(
              LB, tf.linalg.matmul(A, Y_data), lower=True
          )
          / sigma2
      )
  
      # KL[q(x) || p(x)]
      dX_data_var = (
          self.X_data_var
          if self.X_data_var.shape.ndims == 2
          else tf.linalg.diag_part(self.X_data_var)
      )
      NQ = to_default_float(tf.size(self.X_data_mean))
      D = to_default_float(tf.shape(Y_data)[1])
      KL = -0.5 * tf.reduce_sum(tf.math.log(dX_data_var))
      KL += 0.5 * tf.reduce_sum(tf.math.log(self.X_prior_var))
      KL -= 0.5 * NQ
      KL += 0.5 * tf.reduce_sum(
          (
              tf.square(self.X_data_mean - self.X_prior_mean)
              + dX_data_var
          )
          / self.X_prior_var
      )
  
      # compute log marginal bound
      ND = to_default_float(tf.size(Y_data))
      bound = -0.5 * ND * tf.math.log(2 * np.pi * sigma2)
      bound += -0.5 * D * log_det_B
      bound += -0.5 * tf.reduce_sum(tf.square(Y_data)) / sigma2
      bound += 0.5 * tf.reduce_sum(tf.square(c))
      bound += (
          -0.5
          * D
          * (
              tf.reduce_sum(psi0) / sigma2
              - tf.reduce_sum(tf.linalg.diag_part(AAT))
          )
      )
      bound -= KL
      return bound
  
imports: from typing import Optional
  import numpy
  import tensorflow
  from check_shapes import check_shapes, inherit_check_shapes
  from ..None import covariances, kernels, likelihoods
  from ..base import InputData, MeanAndVariance, OutputData, Parameter, RegressionData, TensorType
  from ..config import default_float, default_jitter
  from ..expectations import expectation
  from ..inducing_variables import InducingPoints
  from ..kernels import Kernel
  from ..mean_functions import MeanFunction, Zero
  from ..probability_distributions import DiagonalGaussian
  from ..utilities import assert_params_false, positive, to_default_float
  from ..utilities.ops import pca_reduce
  from .gpr import GPR
  from .model import GPModel
  from .training_mixins import InternalDataTrainingLossMixin
  from .util import InducingVariablesLike, data_input_to_tensor, inducingpoint_wrapper
label_desc: Use image node_id 3 for calling the BayesianGPLVM obj's underlying member method code with example usage: obj.elbo() and returns: bound