class_docstr: Probit GP for preference learning with Laplace approximation
  
  A probit-likelihood GP that learns via pairwise comparison data, using a
  Laplace approximation of the posterior of the estimated utility values. By
  default it uses a scaled RBF kernel.
  
  Implementation is based on [Chu2005preference]_.
  Also see [Brochu2010tutorial]_ for additional reference.
  
  Note that in [Chu2005preference]_ the likelihood of a pairwise comparison
  is :math:`\left(\frac{f(x_1) - f(x_2)}{\sqrt{2}\sigma}\right)`, i.e. a scale is
  used in the denominator. To maintain consistency with usage of kernels
  elsewhere in BoTorch, we instead do not include :math:`\sigma` in the code
  (implicitly setting it to 1) and use ScaleKernel to scale the function.
  
  In the example below, the user/decision maker has stated that they prefer
  the first item over the second item and the third item over the second item,
  generating comparisons [0, 1] and [2, 1].
  Example:
      >>> from botorch.models import PairwiseGP
      >>> import torch
      >>> datapoints = torch.Tensor([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
      >>> comparisons = torch.Tensor([[0, 1], [2, 1]])
      >>> model = PairwiseGP(datapoints, comparisons)
class_docstr_tok: ["Probit","GP","for","preference","learning","with","Laplace","approximation","A","probit-likelihood","GP","that","learns","via","pairwise","comparison","data",",","using","a","Laplace","approximation","of","the","posterior","of","the","estimated","utility","values",".","By","default","it","uses","a","scaled","RBF","kernel",".","Implementation","is","based","on","[","Chu2005preference","]","_",".","Also","see","[","Brochu2010tutorial","]","_","for","additional","reference",".","Note","that","in","[","Chu2005preference","]","_","the","likelihood","of","a","pairwise","comparison","is",":","math",":","`","\\left","(","\\frac","{","f","(","x_1",")","-","f","(","x_2",")","}","{","\\sqrt","{","2","}","\\sigma","}","\\right",")","`",",","i.e",".","a","scale","is","used","in","the","denominator",".","To","maintain","consistency","with","usage","of","kernels","elsewhere","in","BoTorch",",","we","instead","do","not","include",":","math",":","`","\\sigma","`","in","the","code","(","implicitly","setting","it","to","1",")","and","use","ScaleKernel","to","scale","the","function",".","In","the","example","below",",","the","user\/decision","maker","has","stated","that","they","prefer","the","first","item","over","the","second","item","and","the","third","item","over","the","second","item",",","generating","comparisons","[","0",",","1","]","and","[","2",",","1","]",".","Example",":",">",">",">","from","botorch.models","import","PairwiseGP",">",">",">","import","torch",">",">",">","datapoints","=","torch.Tensor","(","[","[","1",",","2",",","3","]",",","[","4",",","5",",","6","]",",","[","7",",","8",",","9","]","]",")",">",">",">","comparisons","=","torch.Tensor","(","[","[","0",",","1","]",",","[","2",",","1","]","]",")",">",">",">","model","=","PairwiseGP","(","datapoints",",","comparisons",")"]
docstr: Update the model by updating the covar matrix and MAP utility values
  
  Update the model by
  1. Re-evaluating the covar matrix as the data or hyperparams may have changed
  2. Approximating maximum a posteriori of the utility function f using fsolve
  
  Should be called after data or hyperparameters are changed to update
  f_map and related values
  
  self._xtol and self._maxfev are passed to fsolve as xtol and maxfev
  to control stopping criteria
  
  Args:
      datapoints: (transformed) datapoints for finding f_max
docstr_tok: ["Update","the","model","by","updating","the","covar","matrix","and","MAP","utility","values","Update","the","model","by","1",".","Re-evaluating","the","covar","matrix","as","the","data","or","hyperparams","may","have","changed","2",".","Approximating","maximum","a","posteriori","of","the","utility","function","f","using","fsolve","Should","be","called","after","data","or","hyperparameters","are","changed","to","update","f_map","and","related","values","self._xtol","and","self._maxfev","are","passed","to","fsolve","as","xtol","and","maxfev","to","control","stopping","criteria","Args",":","datapoints",":","(","transformed",")","datapoints","for","finding","f_max"]
code: def _update(self, datapoints: Tensor, **kwargs) -> None:
      r"""Update the model by updating the covar matrix and MAP utility values
  
      Update the model by
      1. Re-evaluating the covar matrix as the data or hyperparams may have changed
      2. Approximating maximum a posteriori of the utility function f using fsolve
  
      Should be called after data or hyperparameters are changed to update
      f_map and related values
  
      self._xtol and self._maxfev are passed to fsolve as xtol and maxfev
      to control stopping criteria
  
      Args:
          datapoints: (transformed) datapoints for finding f_max
      """
  
      xtol = 1e-6 if self._xtol is None else self._xtol
      maxfev = 100 if self._maxfev is None else self._maxfev
  
      # Using the latest param for covariance before calculating f_map
      self._update_covar(datapoints)
  
      # scipy newton raphson
      with torch.no_grad():
          # warm start
          init_x0_size = self.batch_shape + torch.Size([self.n])
          if (
              self._x0 is None
              or torch.Size(self._x0.shape) != init_x0_size
          ):
              sqrt_scale = (
                  self.covar_module.outputscale.sqrt()
                  .unsqueeze(-1)
                  .detach()
                  .cpu()
                  .numpy()
              )
              # Heuristic intialization using winning count with perturbation
              # to avoid extreme or unprobable likelihood values
              win_count = self.D.sum(dim=-2).detach().cpu().numpy()
              wc_mean, wc_std = (
                  win_count.mean(axis=-1, keepdims=True),
                  win_count.std(axis=-1, keepdims=True).clip(min=1e-6),
              )
              x0 = (win_count - wc_mean) / wc_std
              # adding random perturbation to in case get stuck at strange init values
              x0 = x0 + 0.05 * np.random.standard_normal(init_x0_size)
              # scale x0 to be on roughly the right scale
              x0 = x0 * sqrt_scale
          else:
              x0 = self._x0
  
          if len(self.batch_shape) > 0:
              # batch mode, do optimize.fsolve sequentially on CPU
              # TODO: enable vectorization/parallelization here
              x0 = x0.reshape(-1, self.n)
              dp_v = datapoints.view(-1, self.n, self.dim).cpu()
              D_v = self.D.view(-1, self.m, self.n).cpu()
              ch_v = self.covar_chol.view(-1, self.n, self.n).cpu()
              ci_v = self.covar_inv.view(-1, self.n, self.n).cpu()
              x = np.empty(x0.shape)
              for i in range(x0.shape[0]):
                  fsolve_args = (
                      dp_v[i],
                      D_v[i],
                      ch_v[i],
                      ci_v[i],
                      True,
                  )
                  with warnings.catch_warnings():
                      warnings.filterwarnings(
                          "ignore", category=RuntimeWarning
                      )
                      x[i] = optimize.fsolve(
                          x0=x0[i],
                          func=self._grad_posterior_f,
                          fprime=self._hess_posterior_f,
                          xtol=xtol,
                          maxfev=maxfev,
                          args=fsolve_args,
                          **kwargs,
                      )
              x = x.reshape(*init_x0_size)
          else:
              # fsolve only works on CPU
              fsolve_args = (
                  datapoints.cpu(),
                  self.D.cpu(),
                  self.covar_chol.cpu(),
                  self.covar_inv.cpu(),
                  True,
              )
              with warnings.catch_warnings():
                  warnings.filterwarnings(
                      "ignore", category=RuntimeWarning
                  )
                  x = optimize.fsolve(
                      x0=x0,
                      func=self._grad_posterior_f,
                      fprime=self._hess_posterior_f,
                      xtol=xtol,
                      maxfev=maxfev,
                      args=fsolve_args,
                      **kwargs,
                  )
  
          self._x0 = x.copy()  # save for warm-starting
          f = torch.tensor(
              x, dtype=datapoints.dtype, device=datapoints.device
          )
  
      # To perform hyperparameter optimization, this needs to be recalculated
      # when calling forward() in order to obtain correct gradients
      # self.likelihood_hess is updated here is for the rare case where we
      # do not want to call forward()
      self.likelihood_hess = self.likelihood.negative_log_hessian_sum(
          utility=f, D=self.D
      )
  
      # Lazy update hlcov_eye, which is used in calculating posterior during training
      self.pred_cov_fac_need_update = True
      # fill in dummy values for hlcov_eye so that load_state_dict can function
      hlcov_eye_size = torch.Size(
          (*self.likelihood_hess.shape[:-2], self.n, self.n)
      )
      self.hlcov_eye = torch.empty(hlcov_eye_size)
  
      # Take two newton step on the posterior MAP point to fill
      # in gradients for pytorch. Using 2 instead of 1 since empirically sometimes
      # the first step results in gradients in the order of 1e-7 while the 2nd step
      # allows it go down further to the order of 1e-12 and stay there.
      self.utility = self._util_newton_updates(
          dp=datapoints, x0=f.clone().requires_grad_(True), max_iter=2
      )
  
imports: from __future__ import annotations
  import warnings
  from copy import deepcopy
  from typing import Any, Dict, Iterable, List, Optional, Tuple, Union
  import numpy
  import torch
  from botorch.acquisition.objective import PosteriorTransform
  from botorch.exceptions import UnsupportedError
  from botorch.exceptions.warnings import _get_single_precision_warning, InputDataWarning
  from botorch.models.likelihoods.pairwise import PairwiseLikelihood, PairwiseProbitLikelihood
  from botorch.models.model import FantasizeMixin, Model
  from botorch.models.transforms.input import InputTransform
  from botorch.models.utils.assorted import consolidate_duplicates
  from botorch.posteriors.gpytorch import GPyTorchPosterior
  from botorch.posteriors.posterior import Posterior
  from gpytorch import settings
  from gpytorch.constraints import GreaterThan, Interval
  from gpytorch.distributions.multivariate_normal import MultivariateNormal
  from gpytorch.kernels.rbf_kernel import RBFKernel
  from gpytorch.kernels.scale_kernel import ScaleKernel
  from gpytorch.means.constant_mean import ConstantMean
  from gpytorch.mlls import MarginalLogLikelihood
  from gpytorch.models.gp import GP
  from gpytorch.priors.smoothed_box_prior import SmoothedBoxPrior
  from gpytorch.priors.torch_priors import GammaPrior
  from linear_operator.operators import LinearOperator, RootLinearOperator
  from linear_operator.utils.cholesky import psd_safe_cholesky
  from linear_operator.utils.errors import NotPSDError
  from scipy import optimize
  from torch import float32, float64, Tensor
  from torch.nn.modules.module import _IncompatibleKeys
label_desc: Use image node_id 11 for calling the PairwiseGP obj's underlying member method code with example usage: obj._update(datapoints) without return types