class_docstr: Funsor representing a batched Gaussian log-density function.
  
  Gaussians are the internal representation for joint and conditional
  multivariate normal distributions and multivariate normal likelihoods.
  Mathematically, a Gaussian represents the quadratic log density function::
  
      f(x) = -0.5 * || x @ prec_sqrt - white_vec ||^2
           = -0.5 * < x @ prec_sqrt - white_vec | x @ prec_sqrt - white_vec >
           = -0.5 * < x | prec_sqrt @ prec_sqrt.T | x>
             + < x | prec_sqrt | white_vec > - 0.5 ||white_vec||^2
  
  Internally Gaussians use a square root information filter (SRIF)
  representation consisting of a square root of the precision matrix
  ``prec_sqrt`` and a vector in the whitened space ``white_vec``. This
  representation allows space-efficient construction of Gaussians with
  incomplete information, i.e. with zero eigenvalues in the precision matrix.
  These incomplete log densities arise when making low-dimensional
  observations of higher-dimensional hidden state. Sampling and
  marginalization are supported only for full-rank Gaussians or full-rank
  subsets of Gaussians. See the :meth:`rank` and :meth:`is_full_rank`
  properties.
  
  .. note:: :class:`Gaussian` s are not normalized probability distributions,
      rather they are canonicalized to evaluate to zero log density at their
      maximum: ``f(prec_sqrt \ white_vec) = 0``. Not only are Gaussians
      non-normalized, but they may be rank deficient and non-normalizable, in
      which case sampling and marginalization are supported only un full-rank
      subsets of variables.
  
  :param torch.Tensor white_vec: An batched white noise vector, where
      ``white_vec = prec_sqrt.T @ mean``. Alternatively you can specify one
      of the kwargs ``mean`` or ``info_vec``, which will be converted to
      ``white_vec``.
  :param torch.Tensor prec_sqrt: A batched square root of the positive
      semidefinite precision matrix. This need not be square, and typically
      has shape ``prec_sqrt.shape == white_vec.shape[:-1] + (dim, rank)``,
      where ``dim`` is the total flattened size of real inputs and
      ``rank = white_vec.shape[-1]``.  Alternatively you can specify one of
      the kwargs ``precision``, ``covariance``, or ``scale_tril``, which will
      be converted to ``prec_sqrt``.
  :param OrderedDict inputs: Mapping from name to
      :class:`~funsor.domains.Domain` .
class_docstr_tok: ["Funsor","representing","a","batched","Gaussian","log-density","function",".","Gaussians","are","the","internal","representation","for","joint","and","conditional","multivariate","normal","distributions","and","multivariate","normal","likelihoods",".","Mathematically",",","a","Gaussian","represents","the","quadratic","log","density","function",":",":","f","(","x",")","=","-0.5","*","||","x","@","prec_sqrt","-","white_vec","||^2","=","-0.5","*","<","x","@","prec_sqrt","-","white_vec","|","x","@","prec_sqrt","-","white_vec",">","=","-0.5","*","<","x","|","prec_sqrt","@","prec_sqrt.T","|","x",">","+","<","x","|","prec_sqrt","|","white_vec",">","-","0.5","||white_vec||^2","Internally","Gaussians","use","a","square","root","information","filter","(","SRIF",")","representation","consisting","of","a","square","root","of","the","precision","matrix","``","prec_sqrt","``","and","a","vector","in","the","whitened","space","``","white_vec","``",".","This","representation","allows","space-efficient","construction","of","Gaussians","with","incomplete","information",",","i.e",".","with","zero","eigenvalues","in","the","precision","matrix",".","These","incomplete","log","densities","arise","when","making","low-dimensional","observations","of","higher-dimensional","hidden","state",".","Sampling","and","marginalization","are","supported","only","for","full-rank","Gaussians","or","full-rank","subsets","of","Gaussians",".","See","the",":","meth",":","`","rank","`","and",":","meth",":","`","is_full_rank","`","properties",".","..","note",":",":",":","class",":","`","Gaussian","`","s","are","not","normalized","probability","distributions",",","rather","they","are","canonicalized","to","evaluate","to","zero","log","density","at","their","maximum",":","``","f","(","prec_sqrt","\\","white_vec",")","=","0","``",".","Not","only","are","Gaussians","non-normalized",",","but","they","may","be","rank","deficient","and","non-normalizable",",","in","which","case","sampling","and","marginalization","are","supported","only","un","full-rank","subsets","of","variables",".",":","param","torch.Tensor","white_vec",":","An","batched","white","noise","vector",",","where","``","white_vec","=","prec_sqrt.T","@","mean","``",".","Alternatively","you","can","specify","one","of","the","kwargs","``","mean","``","or","``","info_vec","``",",","which","will","be","converted","to","``","white_vec","``",".",":","param","torch.Tensor","prec_sqrt",":","A","batched","square","root","of","the","positive","semidefinite","precision","matrix",".","This","need","not","be","square",",","and","typically","has","shape","``","prec_sqrt.shape","==","white_vec.shape","[",":","-1","]","+","(","dim",",","rank",")","``",",","where","``","dim","``","is","the","total","flattened","size","of","real","inputs","and","``","rank","=","white_vec.shape","[","-1","]","``",".","Alternatively","you","can","specify","one","of","the","kwargs","``","precision","``",",","``","covariance","``",",","or","``","scale_tril","``",",","which","will","be","converted","to","``","prec_sqrt","``",".",":","param","OrderedDict","inputs",":","Mapping","from","name","to",":","class",":","`","~funsor.domains.Domain","`","."]
code: def _eager_subs_affine(self, subs, remaining_subs):
      # Extract an affine representation.
      affine = OrderedDict()
      for k, v in subs:
          const, coeffs = extract_affine(v)
          if isinstance(const, Tensor) and all(
              isinstance(coeff, Tensor) for coeff, _ in coeffs.values()
          ):
              affine[k] = const, coeffs
          else:
              remaining_subs += ((k, v),)
      if not affine:
          return reflect.interpret(Subs, self, remaining_subs)
  
      # Align integer dimensions.
      old_int_inputs = OrderedDict(
          (k, v) for k, v in self.inputs.items() if v.dtype != "real"
      )
      tensors = [
          Tensor(self.white_vec, old_int_inputs),
          Tensor(self.prec_sqrt, old_int_inputs),
      ]
      for const, coeffs in affine.values():
          tensors.append(const)
          tensors.extend(coeff for coeff, _ in coeffs.values())
      new_int_inputs, tensors = align_tensors(*tensors, expand=True)
      tensors = (Tensor(x, new_int_inputs) for x in tensors)
      white_vec = next(tensors).data
      prec_sqrt = next(tensors).data
      for old_k, (const, coeffs) in affine.items():
          const = next(tensors)
          for new_k, (coeff, eqn) in coeffs.items():
              coeff = next(tensors)
              coeffs[new_k] = coeff, eqn
          affine[old_k] = const, coeffs
      batch_shape = white_vec.shape[:-1]
  
      # Align real dimensions.
      old_real_inputs = OrderedDict(
          (k, v) for k, v in self.inputs.items() if v.dtype == "real"
      )
      new_real_inputs = old_real_inputs.copy()
      for old_k, (const, coeffs) in affine.items():
          del new_real_inputs[old_k]
          for new_k, (coeff, eqn) in coeffs.items():
              new_shape = coeff.shape[
                  : len(eqn.split("->")[0].split(",")[1])
              ]
              new_real_inputs[new_k] = Reals[new_shape]
      old_offsets, old_dim = _compute_offsets(old_real_inputs)
      new_offsets, new_dim = _compute_offsets(new_real_inputs)
      new_inputs = new_int_inputs.copy()
      new_inputs.update(new_real_inputs)
  
      # Construct a blockwise affine representation of the substitution.
      subs_vector = BlockVector(batch_shape + (old_dim,))
      subs_matrix = BlockMatrix(batch_shape + (new_dim, old_dim))
      for old_k, old_offset in old_offsets.items():
          old_size = old_real_inputs[old_k].num_elements
          old_slice = slice(old_offset, old_offset + old_size)
          if old_k in new_real_inputs:
              new_offset = new_offsets[old_k]
              new_slice = slice(new_offset, new_offset + old_size)
              subs_matrix[..., new_slice, old_slice] = ops.new_eye(
                  self.white_vec, batch_shape + (old_size,)
              )
              continue
          const, coeffs = affine[old_k]
          old_shape = old_real_inputs[old_k].shape
          assert const.data.shape == batch_shape + old_shape
          subs_vector[..., old_slice] = const.data.reshape(
              batch_shape + (old_size,)
          )
          for new_k, new_offset in new_offsets.items():
              if new_k in coeffs:
                  coeff, eqn = coeffs[new_k]
                  new_size = new_real_inputs[new_k].num_elements
                  new_slice = slice(new_offset, new_offset + new_size)
                  assert (
                      coeff.shape
                      == new_real_inputs[new_k].shape + old_shape
                  )
                  subs_matrix[
                      ..., new_slice, old_slice
                  ] = coeff.data.reshape(
                      batch_shape + (new_size, old_size)
                  )
      subs_vector = subs_vector.as_tensor()
      subs_matrix = subs_matrix.as_tensor()
  
      # Construct the new Gaussian. Suppose the old Gaussian funsor g has density
      #   G(x;w,P) = -1/2 || x P - w ||^2
      # Now define a new Gaussian by substituting x = A y + b:
      #   G(y;w',P') = G(y A + b; w, P)
      #              = -1/2 || (y A + b) P - w ||^2
      #              = -1/2 || y A P - w + b P ||^2
      #              =: -1/2 || y P' - w' ||^2
      #              = G(y; w - b P, A P)
      # where  P' = A P  and  w' = w - b P  parametrize the new Gaussian.
      white_vec = white_vec - _vm(subs_vector, prec_sqrt)
      prec_sqrt = subs_matrix @ prec_sqrt
      result = Gaussian(white_vec, prec_sqrt, new_inputs)
      return Subs(result, remaining_subs) if remaining_subs else result
  
imports: import math
  from collections import OrderedDict, defaultdict
  from contextlib import contextmanager
  from functools import reduce
  import funsor.ops
  from funsor.affine import affine_inputs, extract_affine, is_affine
  from funsor.delta import Delta
  from funsor.domains import Real, Reals
  from funsor.interpretations import compress_gaussians
  from funsor.ops import AddOp, SubOp
  from funsor.tensor import Tensor, align_tensor, align_tensors
  from funsor.terms import Binary, Funsor, FunsorMeta, Number, Slice, Subs, Variable, eager, reflect
  from funsor.util import broadcast_shape, get_tracing_state, lazy_property
label_desc: Use image node_id 19 for calling the Gaussian obj's underlying member method code with example usage: obj._eager_subs_affine(subs, remaining_subs) and returns: unknown, reflect