Source code for baybe.surrogates.gaussian_process.presets.botorch

"""BoTorch preset for Gaussian process surrogates."""

from __future__ import annotations

import gc
from itertools import chain
from typing import TYPE_CHECKING, ClassVar

import pandas as pd
from attrs import define
from typing_extensions import override

from baybe.kernels.base import Kernel
from baybe.objectives.base import Objective
from baybe.parameters.enum import _ParameterKind
from baybe.searchspace.core import SearchSpace
from baybe.surrogates.gaussian_process.components.fit_criterion import (
    FitCriterion,
    PlainFitCriterionFactory,
)
from baybe.surrogates.gaussian_process.components.kernel import (
    ICMKernelFactory,
    _PureKernelFactory,
)
from baybe.surrogates.gaussian_process.presets.hvarfner import (
    HvarfnerLikelihoodFactory as BotorchLikelihoodFactory,
)
from baybe.surrogates.gaussian_process.presets.hvarfner import (
    HvarfnerMeanFactory as BotorchMeanFactory,
)

if TYPE_CHECKING:
    from gpytorch.kernels import Kernel as GPyTorchKernel

# The minimum BoTorch version required for the preset
_MIN_BOTORCH_VERSION = "0.18.0"


[docs] @define class BotorchKernelFactory(_PureKernelFactory): """A factory providing kernels matching BoTorch's :class:`~botorch.models.MultiTaskGP` defaults.""" # noqa: E501 _uses_parameter_names: ClassVar[bool] = True # See base class. _supported_parameter_kinds: ClassVar[_ParameterKind] = ( _ParameterKind.REGULAR | _ParameterKind.TASK ) # See base class. @override def _make( self, searchspace: SearchSpace, objective: Objective, measurements: pd.DataFrame ) -> Kernel | GPyTorchKernel: self._validate_botorch_version() from botorch.models.kernels.positive_index import PositiveIndexKernel from botorch.models.utils.gpytorch_modules import ( get_covar_module_with_dim_scaled_prior, ) from botorch.models.utils.priors import BetaPrior parameter_names = self.get_parameter_names(searchspace) # For regular parameters, resolve parameter names to active dimension indices active_dims = list( chain.from_iterable( searchspace.get_comp_rep_parameter_indices(name) for name in parameter_names if searchspace.get_parameters_by_name([name])[0]._kind is _ParameterKind.REGULAR ) ) ard_num_dims = len(active_dims) # Create the base kernel for the regular parameters base_kernel = get_covar_module_with_dim_scaled_prior( ard_num_dims=ard_num_dims, active_dims=active_dims ) # Single-task case if (task_idx := searchspace.task_idx) is None: return base_kernel task_prior = BetaPrior(concentration1=2.5, concentration0=1.5) index_kernel = PositiveIndexKernel( num_tasks=searchspace.n_tasks, rank=searchspace.n_tasks, task_prior=task_prior, active_dims=[task_idx], ) return ICMKernelFactory(base_kernel, index_kernel)( searchspace, objective, measurements ) def _validate_botorch_version(self) -> None: """Verify that the installed BoTorch version meets the minimum requirement. Raises: IncompatibilityError: If the installed BoTorch version is too old. """ from importlib.metadata import version from packaging.version import Version from baybe.exceptions import IncompatibilityError installed = version("botorch") if Version(installed) < Version(_MIN_BOTORCH_VERSION): raise IncompatibilityError( f"The '{self.__class__.__name__}' requires botorch>=" f"{_MIN_BOTORCH_VERSION}, but version {installed} is installed. " f"Please upgrade: pip install 'botorch>=" f"{_MIN_BOTORCH_VERSION}'." )
# Collect leftover original slotted classes processed by `attrs.define` gc.collect() # Aliases for generic preset imports KERNEL_FACTORY = BotorchKernelFactory() MEAN_FACTORY = BotorchMeanFactory() LIKELIHOOD_FACTORY = BotorchLikelihoodFactory() FIT_CRITERION_FACTORY = PlainFitCriterionFactory(FitCriterion.MARGINAL_LOG_LIKELIHOOD) __all__ = [ "BotorchKernelFactory", "BotorchLikelihoodFactory", "BotorchMeanFactory", "FIT_CRITERION_FACTORY", "KERNEL_FACTORY", "LIKELIHOOD_FACTORY", "MEAN_FACTORY", ]