class_docstr: Bayesian Optimiser using `GPyOpt` as a backend.
class_docstr_tok: ["Bayesian","Optimiser","using","`","GPyOpt","`","as","a","backend","."]
docstr: Run one step of Bayesian optimisation with a GP regression surrogate
  model.
  
  The first sample of the domain is chosen at random. Only after the model
  has been updated with at least one (data point, evaluation score)-pair
  the GPs are built and the acquisition function computed and optimised.
  
  Args:
      batch_size: (optional) :obj:`int`. The number of samples to suggest
          at once. If larger than one, there is no guarantee for the
          optimality of the number of probes.
      minimise: (optional) :obj:`bool`. Whether the objective should be
          minimised
      **kwargs: optional keyword arguments which will be passed to the
          backend `GPyOpt.methods.BayesianOptimisation` optimiser.
  
  Keyword Args:
      model: :obj:`str` or :obj:`GPy.Model` object. The surrogate model
          used by the backend optimiser.
      kernel: :obj:`GPy.Kern` object. The kernel used by the model.
      variance: :obj:`float`. The variance of the objective function.
  
  Returns:
      A list of `batch_size`-many :class:`Sample` instances at which the
      objective should be evaluated next.
  
  Raises:
      :class:`ExhaustedSearchSpaceError`: if the domain is discrete and
      gets exhausted.
docstr_tok: ["Run","one","step","of","Bayesian","optimisation","with","a","GP","regression","surrogate","model",".","The","first","sample","of","the","domain","is","chosen","at","random",".","Only","after","the","model","has","been","updated","with","at","least","one","(","data","point",",","evaluation","score",")","-pair","the","GPs","are","built","and","the","acquisition","function","computed","and","optimised",".","Args",":","batch_size",":","(","optional",")",":","obj",":","`","int","`",".","The","number","of","samples","to","suggest","at","once",".","If","larger","than","one",",","there","is","no","guarantee","for","the","optimality","of","the","number","of","probes",".","minimise",":","(","optional",")",":","obj",":","`","bool","`",".","Whether","the","objective","should","be","minimised","*","*","kwargs",":","optional","keyword","arguments","which","will","be","passed","to","the","backend","`","GPyOpt.methods.BayesianOptimisation","`","optimiser",".","Keyword","Args",":","model",":",":","obj",":","`","str","`","or",":","obj",":","`","GPy.Model","`","object",".","The","surrogate","model","used","by","the","backend","optimiser",".","kernel",":",":","obj",":","`","GPy.Kern","`","object",".","The","kernel","used","by","the","model",".","variance",":",":","obj",":","`","float","`",".","The","variance","of","the","objective","function",".","Returns",":","A","list","of","`","batch_size","`","-many",":","class",":","`","Sample","`","instances","at","which","the","objective","should","be","evaluated","next",".","Raises",":",":","class",":","`","ExhaustedSearchSpaceError","`",":","if","the","domain","is","discrete","and","gets","exhausted","."]
code: def run_step(
      self, batch_size: int = 1, minimise: bool = False, **kwargs
  ) -> List[Sample]:
      """Run one step of Bayesian optimisation with a GP regression surrogate
      model.
  
      The first sample of the domain is chosen at random. Only after the model
      has been updated with at least one (data point, evaluation score)-pair
      the GPs are built and the acquisition function computed and optimised.
  
      Args:
          batch_size: (optional) :obj:`int`. The number of samples to suggest
              at once. If larger than one, there is no guarantee for the
              optimality of the number of probes.
          minimise: (optional) :obj:`bool`. Whether the objective should be
              minimised
          **kwargs: optional keyword arguments which will be passed to the
              backend `GPyOpt.methods.BayesianOptimisation` optimiser.
  
      Keyword Args:
          model: :obj:`str` or :obj:`GPy.Model` object. The surrogate model
              used by the backend optimiser.
          kernel: :obj:`GPy.Kern` object. The kernel used by the model.
          variance: :obj:`float`. The variance of the objective function.
  
      Returns:
          A list of `batch_size`-many :class:`Sample` instances at which the
          objective should be evaluated next.
  
      Raises:
          :class:`ExhaustedSearchSpaceError`: if the domain is discrete and
          gets exhausted.
      """
      if self.__is_empty_data:
          next_samples = [
              self.domain.sample() for _ in range(batch_size)
          ]
      else:
          assert (
              len(self._data_x) > 0 and len(self._data_fx) > 0
          ), "Cannot initialise BO from empty data."
          default_kwargs = {
              "num_cores": min(batch_size, cpu_count() - 1),
              "normalize_Y": True,
              "acquisition_type": "EI",
              "de_duplication": True,
              "model_type": "GP",
              "evaluator_type": "local_penalization"
              if batch_size > 1
              else "sequential",
          }
          if "model" in kwargs:
              model = kwargs.pop("model")
              # NOTE: Remove this test for model type after the bug in GPyOpt
              #  is fixed: https://github.com/SheffieldML/GPyOpt/issues/183
              if (
                  isinstance(model, str)
                  and model.lower() == "gp_mcmc"
                  and batch_size > 1
              ):
                  raise ValueError(
                      "GP_MCMC model cannot be used with a batch size > 1 "
                      "due to a bug in GPyOpt: "
                      "https://github.com/SheffieldML/GPyOpt/issues/183"
                  )
              kernel = kwargs.pop("kernel", None)
              variance = kwargs.pop("variance", None)
              default_kwargs["model"] = self._build_model(
                  model, kernel, variance
              )
              if variance is not None and all(
                  np.atleast_1d(np.isclose(variance, 0.0))
              ):
                  default_kwargs["exact_feval"] = True
          default_kwargs = _overwrite_dict(default_kwargs, kwargs)
  
          # NOTE: as of GPyOpt 1.2.5 adding new data to an existing model is
          #  not yet possible, hence the object recreation. This behaviour
          #  might be changed in future versions. In this case the code should
          #  be refactored such that `bo` is initialised once and `update`
          #  takes care of the extension of the (X, Y) samples.
          bo = GPyOpt.methods.BayesianOptimization(
              f=None,
              domain=self.gpyopt_domain,
              maximize=not minimise,
              X=self._data_x,
              # NOTE: the following hack is necessary due to a bug in GPyOpt.
              #  The code should be updated once this gets fixed:
              #  https://github.com/SheffieldML/GPyOpt/issues/180
              Y=(-1 + 2 * minimise) * self._data_fx,
              initial_design_numdata=len(self._data_x),
              batch_size=batch_size,
              **default_kwargs,
          )
          try:
              gpyopt_samples = bo.suggest_next_locations()
          except gpyopt_err.FullyExploredOptimizationDomainError as err:
              raise ExhaustedSearchSpaceError from err
          next_samples = [
              self._convert_from_gpyopt_sample(s)
              for s in gpyopt_samples
          ]
      return next_samples
  
imports: from multiprocessing import cpu_count
  from typing import Any, Dict, List, Sequence, Tuple, Type, TypeVar, Union
  import GPy
  import GPyOpt
  import numpy
  from GPyOpt.core import errors
  from hypertunity import utils
  from hypertunity.domain import Domain, Sample
  from hypertunity.optimisation.base import EvaluationScore, ExhaustedSearchSpaceError, Optimiser
label_desc: Use image node_id 5 for calling the BayesianOptimisation obj's underlying member method code with example usage: obj.run_step(batch_size, minimise) and returns: next_samples