diff --git a/CHANGELOG.md b/CHANGELOG.md index 680f11da28..de3165f616 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,9 +16,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Support for GPyTorch objects (kernels, means, likelihood) as Gaussian process components, enabling full low-level customization - Factories for all Gaussian process components -- `EDBO` and `EDBO_SMOOTHED` presets for `GaussianProcessSurrogate` +- `CHEN`, `EDBO` and `EDBO_SMOOTHED` presets for `GaussianProcessSurrogate` - `TypeSelector` and `NameSelector` classes for parameter selection in kernel factories - `parameter_names` attribute to basic kernels for controlling the considered parameters +- `ParameterKind` flag enum for classifying parameters by their role and automatic + parameter kind validation in kernel factories - `IndexKernel` and `PositiveIndexKernel` classes - Interpoint constraints for continuous search spaces - `IndexKernel` and `PositiveIndexKernel` classes diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 6270ff77c0..f639c03a8a 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -39,4 +39,10 @@ - Kathrin Skubch (Merck KGaA, Darmstadt, Germany):\ Transfer learning regression benchmarks infrastructure - Myra Zmarsly (Merck Life Science KGaA, Darmstadt, Germany):\ - Identification of non-dominated parameter configurations \ No newline at end of file + Identification of non-dominated parameter configurations +- Thijs Stuyver (PSL University, Paris, France):\ + Adaptive hyper-prior tailored for reaction yield optimization tasks +- Maximilian Fleck (PSL University, Paris, France):\ + Adaptive hyper-prior tailored for reaction yield optimization tasks +- Guanming Chen (PSL University, Paris, France):\ + Adaptive hyper-prior tailored for reaction yield optimization tasks diff --git a/baybe/parameters/__init__.py b/baybe/parameters/__init__.py index 93e62b6ee9..6fe2c0d8f4 100644 --- a/baybe/parameters/__init__.py +++ b/baybe/parameters/__init__.py @@ -5,6 +5,7 @@ from baybe.parameters.enum import ( CategoricalEncoding, CustomEncoding, + ParameterKind, SubstanceEncoding, ) from baybe.parameters.numerical import ( @@ -22,6 +23,7 @@ "MeasurableMetadata", "NumericalContinuousParameter", "NumericalDiscreteParameter", + "ParameterKind", "SubstanceEncoding", "SubstanceParameter", "TaskParameter", diff --git a/baybe/parameters/base.py b/baybe/parameters/base.py index 2d4df2bc77..7986a21124 100644 --- a/baybe/parameters/base.py +++ b/baybe/parameters/base.py @@ -21,6 +21,7 @@ from baybe.utils.metadata import MeasurableMetadata, to_metadata if TYPE_CHECKING: + from baybe.parameters.enum import ParameterKind from baybe.searchspace.continuous import SubspaceContinuous from baybe.searchspace.core import SearchSpace from baybe.searchspace.discrete import SubspaceDiscrete @@ -77,6 +78,13 @@ def is_discrete(self) -> bool: """Boolean indicating if this is a discrete parameter.""" return isinstance(self, DiscreteParameter) + @property + def kind(self) -> ParameterKind: + """The kind of the parameter.""" + from baybe.parameters.enum import ParameterKind + + return ParameterKind.from_parameter(self) + @property @abstractmethod def comp_rep_columns(self) -> tuple[str, ...]: diff --git a/baybe/parameters/enum.py b/baybe/parameters/enum.py index 3161f67dc7..07e9f9fb34 100644 --- a/baybe/parameters/enum.py +++ b/baybe/parameters/enum.py @@ -1,6 +1,38 @@ """Parameter-related enumerations.""" -from enum import Enum +from __future__ import annotations + +from enum import Enum, Flag, auto +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from baybe.parameters.base import Parameter + + +class ParameterKind(Flag): + """Flag enum encoding the kind of a parameter. + + Can be used to express compatibility (e.g. Gaussian process kernel factories) + with different parameter types via bitwise combination of flags. + """ + + REGULAR = auto() + """Regular parameter undergoing no special treatment.""" + + TASK = auto() + """Task parameter for transfer learning.""" + + FIDELITY = auto() + """Fidelity parameter for multi-fidelity modelling.""" + + @staticmethod + def from_parameter(parameter: Parameter) -> ParameterKind: + """Determine the kind of a parameter from its type.""" + from baybe.parameters.categorical import TaskParameter + + if isinstance(parameter, TaskParameter): + return ParameterKind.TASK + return ParameterKind.REGULAR class ParameterEncoding(Enum): diff --git a/baybe/parameters/selectors.py b/baybe/parameters/selectors.py index 6b1a4ae16d..e72b6fe4d9 100644 --- a/baybe/parameters/selectors.py +++ b/baybe/parameters/selectors.py @@ -3,15 +3,13 @@ import re from abc import ABC, abstractmethod from collections.abc import Collection -from typing import ClassVar, Protocol +from typing import Protocol from attrs import Converter, define, field -from attrs.converters import optional from attrs.validators import deep_iterable, instance_of, min_len from typing_extensions import override from baybe.parameters.base import Parameter -from baybe.searchspace.core import SearchSpace from baybe.utils.basic import to_tuple from baybe.utils.conversion import nonstring_to_tuple @@ -131,37 +129,3 @@ def to_parameter_selector( return TypeSelector(items) raise TypeError(f"Cannot convert {x!r} to a parameter selector.") - - -@define -class _ParameterSelectorMixin: - """A mixin class to enable parameter selection.""" - - # For internal use only: sanity check mechanism to remind developers of new - # subclasses to actually use the parameter selector when it is provided - # TODO: Perhaps we can find a more elegant way to enforce this by design - _uses_parameter_names: ClassVar[bool] = False - - parameter_selector: ParameterSelectorProtocol | None = field( - default=None, converter=optional(to_parameter_selector), kw_only=True - ) - """An optional selector to specify which parameters are to be considered.""" - - def get_parameter_names(self, searchspace: SearchSpace) -> tuple[str, ...] | None: - """Get the names of the parameters to be considered.""" - if self.parameter_selector is None: - return None - - return tuple( - p.name for p in searchspace.parameters if self.parameter_selector(p) - ) - - def __attrs_post_init__(self): - if self.parameter_selector is not None and not self._uses_parameter_names: - raise AssertionError( - f"A `parameter_selector` was provided to " - f"`{type(self).__name__}`, but the class does not set " - f"`_uses_parameter_names = True`. Subclasses that accept a " - f"parameter selector must explicitly set this flag to confirm " - f"they actually use the selected parameter names." - ) diff --git a/baybe/surrogates/gaussian_process/components/kernel.py b/baybe/surrogates/gaussian_process/components/kernel.py index ca4da066e7..65b6e5e454 100644 --- a/baybe/surrogates/gaussian_process/components/kernel.py +++ b/baybe/surrogates/gaussian_process/components/kernel.py @@ -2,18 +2,24 @@ from __future__ import annotations +from abc import ABC, abstractmethod +from collections.abc import Iterable from functools import partial -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, ClassVar from attrs import define, field +from attrs.converters import optional from attrs.validators import is_callable from typing_extensions import override +from baybe.exceptions import IncompatibleSearchSpaceError from baybe.kernels.base import Kernel -from baybe.kernels.composite import ProductKernel from baybe.parameters.categorical import TaskParameter +from baybe.parameters.enum import ParameterKind from baybe.parameters.selectors import ( + ParameterSelectorProtocol, TypeSelector, + to_parameter_selector, ) from baybe.searchspace.core import SearchSpace from baybe.surrogates.gaussian_process.components.generic import ( @@ -27,6 +33,8 @@ from gpytorch.kernels import Kernel as GPyTorchKernel from torch import Tensor + from baybe.parameters.base import Parameter + KernelFactoryProtocol = GPComponentFactoryProtocol[Kernel | GPyTorchKernel] PlainKernelFactory = PlainGPComponentFactory[Kernel | GPyTorchKernel] else: @@ -35,6 +43,80 @@ PlainKernelFactory = PlainGPComponentFactory[Kernel] +@define +class _KernelFactory(KernelFactoryProtocol, ABC): + """Base class for kernel factories.""" + + # For internal use only: sanity check mechanism to remind developers of new + # factories to actually use the parameter selector when it is provided + # TODO: Perhaps we can find a more elegant way to enforce this by design + _uses_parameter_names: ClassVar[bool] = False + + supported_parameter_kinds: ClassVar[ParameterKind] = ParameterKind.REGULAR + """The parameter kinds supported by the kernel factory.""" + + parameter_selector: ParameterSelectorProtocol | None = field( + default=None, converter=optional(to_parameter_selector) + ) + """An optional selector to specify which parameters are considered by the kernel.""" + + def get_parameter_names(self, searchspace: SearchSpace) -> tuple[str, ...] | None: + """Get the names of the parameters to be considered by the kernel.""" + if self.parameter_selector is None: + return None + + return tuple( + p.name for p in searchspace.parameters if self.parameter_selector(p) + ) + + def _validate_parameter_kinds(self, parameters: Iterable[Parameter]) -> None: + """Validate that the given parameters are supported by the factory. + + Args: + parameters: The parameters to validate. + + Raises: + IncompatibleSearchSpaceError: If unsupported parameter kinds are found. + """ + if unsupported := [ + p.name for p in parameters if not (p.kind & self.supported_parameter_kinds) + ]: + raise IncompatibleSearchSpaceError( + f"'{type(self).__name__}' does not support parameter kind(s) for " + f"parameter(s) {unsupported}. Supported kinds: " + f"{self.supported_parameter_kinds}." + ) + + @override + def __call__( + self, searchspace: SearchSpace, train_x: Tensor, train_y: Tensor + ) -> Kernel: + """Construct the kernel, validating parameter kinds before construction.""" + if self.parameter_selector is not None: + params = [p for p in searchspace.parameters if self.parameter_selector(p)] + else: + params = list(searchspace.parameters) + self._validate_parameter_kinds(params) + + return self._make(searchspace, train_x, train_y) + + @abstractmethod + def _make( + self, searchspace: SearchSpace, train_x: Tensor, train_y: Tensor + ) -> Kernel: + """Construct the kernel.""" + + def __attrs_post_init__(self): + if self.parameter_selector is not None and not self._uses_parameter_names: + raise AssertionError( + f"A `parameter_selector` was provided to " + f"`{type(self).__name__}`, but the class does not set " + f"`_uses_parameter_names = True`. Subclasses that accept a " + f"parameter selector must explicitly set this flag to confirm " + f"they actually use the selected parameter names." + ) + + @define class ICMKernelFactory(KernelFactoryProtocol): """A kernel factory that constructs an ICM kernel for transfer learning. @@ -78,4 +160,8 @@ def __call__( ) -> Kernel: base_kernel = self.base_kernel_factory(searchspace, train_x, train_y) task_kernel = self.task_kernel_factory(searchspace, train_x, train_y) - return ProductKernel([base_kernel, task_kernel]) + if isinstance(base_kernel, Kernel): + base_kernel = base_kernel.to_gpytorch(searchspace) + if isinstance(task_kernel, Kernel): + task_kernel = task_kernel.to_gpytorch(searchspace) + return base_kernel * task_kernel diff --git a/baybe/surrogates/gaussian_process/presets/__init__.py b/baybe/surrogates/gaussian_process/presets/__init__.py index deb7de9e64..434fbf560f 100644 --- a/baybe/surrogates/gaussian_process/presets/__init__.py +++ b/baybe/surrogates/gaussian_process/presets/__init__.py @@ -7,6 +7,9 @@ BayBEMeanFactory, ) +# Chen preset +from baybe.surrogates.gaussian_process.presets.chen import CHENKernelFactory + # Core from baybe.surrogates.gaussian_process.presets.core import GaussianProcessPreset @@ -31,6 +34,8 @@ "BayBEKernelFactory", "BayBELikelihoodFactory", "BayBEMeanFactory", + # Chen preset + "CHENKernelFactory", # EDBO preset "EDBOKernelFactory", "EDBOLikelihoodFactory", diff --git a/baybe/surrogates/gaussian_process/presets/baybe.py b/baybe/surrogates/gaussian_process/presets/baybe.py index bc820ae52b..e96a08e6cc 100644 --- a/baybe/surrogates/gaussian_process/presets/baybe.py +++ b/baybe/surrogates/gaussian_process/presets/baybe.py @@ -10,14 +10,14 @@ from baybe.kernels.base import Kernel from baybe.kernels.basic import IndexKernel from baybe.parameters.categorical import TaskParameter +from baybe.parameters.enum import ParameterKind from baybe.parameters.selectors import ( ParameterSelectorProtocol, TypeSelector, - _ParameterSelectorMixin, to_parameter_selector, ) from baybe.searchspace.core import SearchSpace -from baybe.surrogates.gaussian_process.components.kernel import KernelFactoryProtocol +from baybe.surrogates.gaussian_process.components.kernel import _KernelFactory from baybe.surrogates.gaussian_process.components.mean import LazyConstantMeanFactory from baybe.surrogates.gaussian_process.presets.edbo_smoothed import ( SmoothedEDBOKernelFactory, @@ -29,11 +29,16 @@ @define -class BayBEKernelFactory(KernelFactoryProtocol): +class BayBEKernelFactory(_KernelFactory): """The default kernel factory for Gaussian process surrogates.""" + supported_parameter_kinds: ClassVar[ParameterKind] = ( + ParameterKind.REGULAR | ParameterKind.TASK + ) + # See base class. + @override - def __call__( + def _make( self, searchspace: SearchSpace, train_x: Tensor, train_y: Tensor ) -> Kernel: from baybe.surrogates.gaussian_process.components.kernel import ICMKernelFactory @@ -48,12 +53,15 @@ def __call__( @define -class BayBETaskKernelFactory(KernelFactoryProtocol, _ParameterSelectorMixin): +class BayBETaskKernelFactory(_KernelFactory): """The factory providing the default task kernel for Gaussian process surrogates.""" _uses_parameter_names: ClassVar[bool] = True # See base class. + supported_parameter_kinds: ClassVar[ParameterKind] = ParameterKind.TASK + # See base class. + parameter_selector: ParameterSelectorProtocol | None = field( factory=lambda: TypeSelector([TaskParameter]), converter=to_parameter_selector, @@ -61,7 +69,7 @@ class BayBETaskKernelFactory(KernelFactoryProtocol, _ParameterSelectorMixin): # TODO: Reuse base attribute (https://github.com/python-attrs/attrs/pull/1429) @override - def __call__( + def _make( self, searchspace: SearchSpace, train_x: Tensor, train_y: Tensor ) -> Kernel: return IndexKernel( diff --git a/baybe/surrogates/gaussian_process/presets/chen.py b/baybe/surrogates/gaussian_process/presets/chen.py new file mode 100644 index 0000000000..014cdb15b1 --- /dev/null +++ b/baybe/surrogates/gaussian_process/presets/chen.py @@ -0,0 +1,63 @@ +"""Preset for adaptive kernel hyperpriors proposed by :cite:p:`Chen2026`.""" + +from __future__ import annotations + +import gc +import math +from typing import TYPE_CHECKING, ClassVar + +from attrs import define +from typing_extensions import override + +from baybe.kernels.basic import MaternKernel +from baybe.kernels.composite import ScaleKernel +from baybe.priors.basic import GammaPrior +from baybe.surrogates.gaussian_process.components.kernel import _KernelFactory +from baybe.surrogates.gaussian_process.presets.baybe import ( + BayBELikelihoodFactory, + BayBEMeanFactory, +) + +if TYPE_CHECKING: + from torch import Tensor + + from baybe.kernels.base import Kernel + from baybe.searchspace.core import SearchSpace + + +@define +class CHENKernelFactory(_KernelFactory): + """A factory providing adaptive hyperprior kernels as proposed by :cite:p:`Chen2026`.""" # noqa: E501 + + _uses_parameter_names: ClassVar[bool] = True + # See base class. + + @override + def _make( + self, searchspace: SearchSpace, train_x: Tensor, train_y: Tensor + ) -> Kernel: + lengthscale = 0.4 * math.sqrt(train_x.shape[-1]) + 4.0 + lengthscale_prior = GammaPrior(2.0 * lengthscale, 2.0) + lengthscale_initial_value = lengthscale + outputscale_prior = GammaPrior(1.0 * lengthscale, 1.0) + outputscale_initial_value = lengthscale + + return ScaleKernel( + MaternKernel( + nu=2.5, + lengthscale_prior=lengthscale_prior, + lengthscale_initial_value=lengthscale_initial_value, + parameter_names=self.get_parameter_names(searchspace), + ), + outputscale_prior=outputscale_prior, + outputscale_initial_value=outputscale_initial_value, + ) + + +# Collect leftover original slotted classes processed by `attrs.define` +gc.collect() + +# Aliases for generic preset imports +PresetKernelFactory = CHENKernelFactory +PresetMeanFactory = BayBEMeanFactory +PresetLikelihoodFactory = BayBELikelihoodFactory diff --git a/baybe/surrogates/gaussian_process/presets/core.py b/baybe/surrogates/gaussian_process/presets/core.py index ad77df0b4d..5347cf85e5 100644 --- a/baybe/surrogates/gaussian_process/presets/core.py +++ b/baybe/surrogates/gaussian_process/presets/core.py @@ -11,8 +11,11 @@ class GaussianProcessPreset(Enum): BAYBE = "BAYBE" """The default BayBE settings of the Gaussian process surrogate class.""" + CHEN = "CHEN" + """The adaptive kernel hyperprior settings proposed by :cite:p:`Chen2026`.""" + EDBO = "EDBO" - """The EDBO settings.""" + """The EDBO settings proposed by :cite:p:`Shields2021`.""" EDBO_SMOOTHED = "EDBO_SMOOTHED" - """A smoothed version of the EDBO settings.""" + """A smoothed version of the EDBO settings (adapted from :cite:p:`Shields2021`).""" diff --git a/baybe/surrogates/gaussian_process/presets/edbo.py b/baybe/surrogates/gaussian_process/presets/edbo.py index 36611c258e..fac4480e73 100644 --- a/baybe/surrogates/gaussian_process/presets/edbo.py +++ b/baybe/surrogates/gaussian_process/presets/edbo.py @@ -1,4 +1,4 @@ -"""EDBO preset for Gaussian process surrogates.""" +"""EDBO preset :cite:p:`Shields2021`.""" from __future__ import annotations @@ -16,13 +16,14 @@ from baybe.parameters.selectors import ( ParameterSelectorProtocol, TypeSelector, - _ParameterSelectorMixin, to_parameter_selector, ) from baybe.parameters.substance import SubstanceParameter from baybe.priors.basic import GammaPrior from baybe.searchspace.discrete import SubspaceDiscrete -from baybe.surrogates.gaussian_process.components.kernel import KernelFactoryProtocol +from baybe.surrogates.gaussian_process.components.kernel import ( + _KernelFactory, +) from baybe.surrogates.gaussian_process.components.likelihood import ( LikelihoodFactoryProtocol, ) @@ -56,12 +57,10 @@ def _contains_encoding( @define -class EDBOKernelFactory(KernelFactoryProtocol, _ParameterSelectorMixin): - """A factory providing EDBO kernels. +class EDBOKernelFactory(_KernelFactory): + """A factory providing EDBO kernels, as proposed by :cite:p:`Shields2021`. - References: - * https://github.com/b-shields/edbo/blob/master/edbo/bro.py#L664 - * https://doi.org/10.1038/s41586-021-03213-y + Github repository: https://github.com/b-shields/edbo """ _uses_parameter_names: ClassVar[bool] = True @@ -74,12 +73,10 @@ class EDBOKernelFactory(KernelFactoryProtocol, _ParameterSelectorMixin): # TODO: Reuse base attribute (https://github.com/python-attrs/attrs/pull/1429) @override - def __call__( + def _make( self, searchspace: SearchSpace, train_x: Tensor, train_y: Tensor ) -> Kernel: - effective_dims = train_x.shape[-1] - len( - [p for p in searchspace.parameters if isinstance(p, TaskParameter)] - ) + effective_dims = train_x.shape[-1] switching_condition = _contains_encoding( searchspace.discrete, _EDBO_ENCODINGS @@ -131,11 +128,9 @@ def __call__( @define class EDBOLikelihoodFactory(LikelihoodFactoryProtocol): - """A factory providing EDBO likelihoods. + """A factory providing EDBO likelihoods, as proposed by :cite:p:`Shields2021`. - References: - * https://github.com/b-shields/edbo/blob/master/edbo/bro.py#L664 - * https://doi.org/10.1038/s41586-021-03213-y + Github repository: https://github.com/b-shields/edbo """ @override diff --git a/baybe/surrogates/gaussian_process/presets/edbo_smoothed.py b/baybe/surrogates/gaussian_process/presets/edbo_smoothed.py index eb8d57491a..16b3c2c26c 100644 --- a/baybe/surrogates/gaussian_process/presets/edbo_smoothed.py +++ b/baybe/surrogates/gaussian_process/presets/edbo_smoothed.py @@ -1,4 +1,4 @@ -"""Smoothed EDBO preset for Gaussian process surrogates.""" +"""Smoothed EDBO preset (adapted from :cite:p:`Shields2021`).""" from __future__ import annotations @@ -15,11 +15,12 @@ from baybe.parameters.selectors import ( ParameterSelectorProtocol, TypeSelector, - _ParameterSelectorMixin, to_parameter_selector, ) from baybe.priors.basic import GammaPrior -from baybe.surrogates.gaussian_process.components.kernel import KernelFactoryProtocol +from baybe.surrogates.gaussian_process.components.kernel import ( + _KernelFactory, +) from baybe.surrogates.gaussian_process.components.likelihood import ( LikelihoodFactoryProtocol, ) @@ -38,13 +39,13 @@ @define -class SmoothedEDBOKernelFactory(KernelFactoryProtocol, _ParameterSelectorMixin): - """A factory providing smoothed versions of EDBO kernels. +class SmoothedEDBOKernelFactory(_KernelFactory): + """A factory providing smoothed versions of EDBO kernels (adapted from :cite:p:`Shields2021`). Takes the low and high dimensional limits of :class:`baybe.surrogates.gaussian_process.presets.edbo.EDBOKernelFactory` and interpolates the prior moments linearly in between. - """ + """ # noqa: E501 _uses_parameter_names: ClassVar[bool] = True # See base class. @@ -56,12 +57,10 @@ class SmoothedEDBOKernelFactory(KernelFactoryProtocol, _ParameterSelectorMixin): # TODO: Reuse base attribute (https://github.com/python-attrs/attrs/pull/1429) @override - def __call__( + def _make( self, searchspace: SearchSpace, train_x: Tensor, train_y: Tensor ) -> Kernel: - effective_dims = train_x.shape[-1] - len( - [p for p in searchspace.parameters if isinstance(p, TaskParameter)] - ) + effective_dims = train_x.shape[-1] # Interpolate prior moments linearly between low D and high D regime. # The high D regime itself is the average of the EDBO OHE and Mordred regime. @@ -95,12 +94,12 @@ def __call__( @define class SmoothedEDBOLikelihoodFactory(LikelihoodFactoryProtocol): - """A factory providing smoothed versions of EDBO likelihoods. + """A factory providing smoothed versions of EDBO likelihoods (adapted from :cite:p:`Shields2021`). Takes the low and high dimensional limits of :class:`baybe.surrogates.gaussian_process.presets.edbo.EDBOLikelihoodFactory` and interpolates the prior moments linearly in between. - """ + """ # noqa: E501 @override def __call__( diff --git a/docs/conf.py b/docs/conf.py index 5251bb8654..6096369714 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -150,7 +150,7 @@ ("py:class", "baybe.acquisition.acqfs._ExpectedHypervolumeImprovement"), ("py:class", "baybe.settings._SlottedContextDecorator"), ("py:class", "baybe.surrogates.gaussian_process.components.PlainKernelFactory"), - ("py:class", "baybe.parameters.selectors._ParameterSelectorMixin"), + ("py:class", "baybe.surrogates.gaussian_process.components.kernel._KernelFactory"), # Deprecation ("py:.*", "baybe.targets._deprecated.*"), ] diff --git a/docs/index.md b/docs/index.md index 3f8ba25cd9..31d138a369 100644 --- a/docs/index.md +++ b/docs/index.md @@ -40,6 +40,7 @@ Contribute Contributors Known Issues Changelog +References Github License ``` diff --git a/docs/misc/references.md b/docs/misc/references.md new file mode 100644 index 0000000000..d977a9bd5b --- /dev/null +++ b/docs/misc/references.md @@ -0,0 +1,4 @@ +# References + +```{bibliography} +``` diff --git a/docs/references.bib b/docs/references.bib index 307529d299..4d159cb686 100644 --- a/docs/references.bib +++ b/docs/references.bib @@ -8,3 +8,22 @@ @inproceedings{NIPS2007_66368270 volume = {20}, year = {2007} } + +@article{Chen2026, + author = {Chen, Guanming and Fleck, Maximilian and Stuyver, Thijs}, + title = {Leveraging Hidden-Space Representations Effectively in Bayesian Optimization for Experiment Design through Dimension-Aware Hyperpriors}, + journal = {ChemRxiv}, + year = {2026}, + doi = {10.26434/chemrxiv.10001986/v2} +} + +@article{Shields2021, + author = {Shields, Benjamin J. and Stevens, Jason and Li, Jun and Parasram, Marvin and Damani, Farhan and Alvarado, Jesus I. Martinez and Janey, Jacob M. and Adams, Ryan P. and Doyle, Abigail G.}, + title = {Bayesian reaction optimization as a tool for chemical synthesis}, + journal = {Nature}, + volume = {590}, + number = {7844}, + pages = {89--96}, + year = {2021}, + doi = {10.1038/s41586-021-03213-y} +} diff --git a/docs/userguide/transfer_learning.md b/docs/userguide/transfer_learning.md index 6f509df31b..3273e20853 100644 --- a/docs/userguide/transfer_learning.md +++ b/docs/userguide/transfer_learning.md @@ -185,7 +185,4 @@ on the optimization: :class: only-dark ``` -```{bibliography} -``` - [`TaskParameter`]: baybe.parameters.categorical.TaskParameter \ No newline at end of file diff --git a/tests/test_kernel_factories.py b/tests/test_kernel_factories.py new file mode 100644 index 0000000000..6a8acda6a6 --- /dev/null +++ b/tests/test_kernel_factories.py @@ -0,0 +1,75 @@ +"""Tests for kernel factories.""" + +from contextlib import nullcontext + +import pytest +import torch +from pytest import param + +from baybe.exceptions import IncompatibleSearchSpaceError +from baybe.parameters.categorical import CategoricalParameter, TaskParameter +from baybe.parameters.numerical import ( + NumericalContinuousParameter, + NumericalDiscreteParameter, +) +from baybe.searchspace.core import SearchSpace +from baybe.surrogates.gaussian_process.presets.baybe import ( + BayBEKernelFactory, + BayBENumericalKernelFactory, + BayBETaskKernelFactory, +) + +# A selector that accepts all parameters +_SELECT_ALL = lambda parameter: True # noqa: E731 + + +@pytest.mark.parametrize( + ("factory", "parameters", "error"), + [ + param( + BayBENumericalKernelFactory(parameter_selector=_SELECT_ALL), + [TaskParameter("task", ["t1", "t2"])], + IncompatibleSearchSpaceError, + id="regular_rejects_task", + ), + param( + BayBETaskKernelFactory(parameter_selector=_SELECT_ALL), + [CategoricalParameter("cat", ["a", "b"])], + IncompatibleSearchSpaceError, + id="task_rejects_categorical", + ), + param( + BayBETaskKernelFactory(parameter_selector=_SELECT_ALL), + [NumericalDiscreteParameter("num", [1, 2, 3])], + IncompatibleSearchSpaceError, + id="task_rejects_numerical_discrete", + ), + param( + BayBETaskKernelFactory(parameter_selector=_SELECT_ALL), + [NumericalContinuousParameter("cont", (0, 1))], + IncompatibleSearchSpaceError, + id="task_rejects_numerical_continuous", + ), + param( + BayBEKernelFactory(), + [ + NumericalContinuousParameter("cont", (0, 1)), + TaskParameter("task", ["t1", "t2"]), + ], + None, + id="combined_accepts_both", + ), + ], +) +def test_factory_parameter_kind_validation(factory, parameters, error): + """Factories reject unsupported parameter kinds and accept supported ones.""" + ss = SearchSpace.from_product(parameters) + train_x = torch.zeros(2, len(ss.comp_rep_columns)) + train_y = torch.zeros(2, 1) + + with ( + nullcontext() + if error is None + else pytest.raises(error, match="does not support") + ): + factory(ss, train_x, train_y)