class_docstr: Non-normalized GammaGaussian distribution:
  
      GammaGaussian(x, s) ~ (concentration + 0.5 * dim - 1) * log(s)
                            - rate * s - s * 0.5 * info_vec.T @ inv(precision) @ info_vec)
                            - s * 0.5 * x.T @ precision @ x + s * x.T @ info_vec,
  
  which will be reparameterized as
  
      GammaGaussian(x, s) =: alpha * log(s) + s * (-0.5 * x.T @ precision @ x + x.T @ info_vec - beta).
  
  The `s` variable plays the role of a mixing variable such that
  
      p(x | s) ~ Gaussian(s * info_vec, s * precision).
  
  Conditioned on `s`, this represents an arbitrary semidefinite quadratic function,
  which can be interpreted as a rank-deficient Gaussian distribution.
  The precision matrix may have zero eigenvalues, thus it may be impossible
  to work directly with the covariance matrix.
  
  :param torch.Tensor log_normalizer: a normalization constant, which is mainly used to keep
      track of normalization terms during contractions.
  :param torch.Tensor info_vec: information vector, which is a scaled version of the mean
      ``info_vec = precision @ mean``. We use this represention to make gaussian contraction
      fast and stable.
  :param torch.Tensor precision: precision matrix of this gaussian.
  :param torch.Tensor alpha: reparameterized shape parameter of the marginal Gamma distribution of
      `s`. The shape parameter Gamma.concentration is reparameterized by:
  
          alpha = Gamma.concentration + 0.5 * dim - 1
  
  :param torch.Tensor beta: reparameterized rate parameter of the marginal Gamma distribution of
      `s`. The rate parameter Gamma.rate is reparameterized by:
  
          beta = Gamma.rate + 0.5 * info_vec.T @ inv(precision) @ info_vec
docstr: Marginalizing out variables on either side of the event dimension::
  
      g.marginalize(left=n).event_logsumexp() = g.event_logsumexp()
      g.marginalize(right=n).event_logsumexp() = g.event_logsumexp()
  
  and for data ``x``:
  
      g.condition(x).event_logsumexp().log_density(s)
        = g.marginalize(left=g.dim() - x.size(-1)).log_density(x, s)
docstr_tok: ["Marginalizing","out","variables","on","either","side","of","the","event","dimension",":",":","g.marginalize","(","left=n",")",".event_logsumexp","(",")","=","g.event_logsumexp","(",")","g.marginalize","(","right=n",")",".event_logsumexp","(",")","=","g.event_logsumexp","(",")","and","for","data","``","x","``",":","g.condition","(","x",")",".event_logsumexp","(",")",".log_density","(","s",")","=","g.marginalize","(","left=g.dim","(",")","-","x.size","(","-1",")",")",".log_density","(","x",",","s",")"]
code: def marginalize(self, left=0, right=0):
      """
      Marginalizing out variables on either side of the event dimension::
  
          g.marginalize(left=n).event_logsumexp() = g.event_logsumexp()
          g.marginalize(right=n).event_logsumexp() = g.event_logsumexp()
  
      and for data ``x``:
  
          g.condition(x).event_logsumexp().log_density(s)
            = g.marginalize(left=g.dim() - x.size(-1)).log_density(x, s)
      """
      # NB: the easiest way to think about this process is to consider GammaGaussian
      # as a Gaussian with precision and info_vec scaled by `s`.
      if left == 0 and right == 0:
          return self
      if left > 0 and right > 0:
          raise NotImplementedError
      n = self.dim()
      n_b = left + right
      a = slice(left, n - right)  # preserved
      b = slice(None, left) if left else slice(n - right, None)
  
      P_aa = self.precision[..., a, a]
      P_ba = self.precision[..., b, a]
      P_bb = self.precision[..., b, b]
      P_b = torch.linalg.cholesky(P_bb)
      P_a = torch.linalg.solve_triangular(P_b, P_ba, upper=False)
      P_at = P_a.transpose(-1, -2)
      precision = P_aa - P_at.matmul(P_a)
  
      info_a = self.info_vec[..., a]
      info_b = self.info_vec[..., b]
      b_tmp = torch.linalg.solve_triangular(
          P_b, info_b.unsqueeze(-1), upper=False
      )
      info_vec = info_a
      if n_b < n:
          info_vec = info_vec - P_at.matmul(b_tmp).squeeze(-1)
  
      alpha = self.alpha - 0.5 * n_b
      beta = self.beta - 0.5 * b_tmp.squeeze(-1).pow(2).sum(-1)
      log_normalizer = (
          self.log_normalizer
          + 0.5 * n_b * math.log(2 * math.pi)
          - P_b.diagonal(dim1=-2, dim2=-1).log().sum(-1)
      )
      return GammaGaussian(
          log_normalizer, info_vec, precision, alpha, beta
      )
  
imports: import math
  import torch
  from torch.distributions.utils import lazy_property
  from torch.nn.functional import pad
  from pyro.distributions.multivariate_studentt import MultivariateStudentT
  from pyro.distributions.torch import MultivariateNormal
  from pyro.distributions.util import broadcast_shape
  from pyro.ops.tensor_utils import precision_to_scale_tril
label_desc: Use image node_id 13 for calling the GammaGaussian obj's underlying member method code with example usage: obj.marginalize(left, right) and returns: GammaGaussian, self