From e3fe5e8cd6c846896d10ad03bc32cc29011f6e45 Mon Sep 17 00:00:00 2001 From: linznedd Date: Thu, 5 Oct 2023 13:18:21 +0200 Subject: [PATCH 01/29] add polynomial kernel --- bofire/data_models/kernels/api.py | 3 +++ bofire/data_models/kernels/continuous.py | 6 ++++++ bofire/kernels/mapper.py | 15 +++++++++++++++ tests/bofire/data_models/test_kernels.py | 21 +++++++++++++++++++++ 4 files changed, 45 insertions(+) diff --git a/bofire/data_models/kernels/api.py b/bofire/data_models/kernels/api.py index 4f9eaaa63..975be871a 100644 --- a/bofire/data_models/kernels/api.py +++ b/bofire/data_models/kernels/api.py @@ -13,6 +13,7 @@ ContinuousKernel, LinearKernel, MaternKernel, + PolynomialKernel, RBFKernel, ) from bofire.data_models.kernels.kernel import Kernel @@ -23,6 +24,7 @@ AnyContinuousKernel = Union[ MaternKernel, LinearKernel, + PolynomialKernel, RBFKernel, ] @@ -36,6 +38,7 @@ ScaleKernel, HammondDistanceKernel, LinearKernel, + PolynomialKernel, MaternKernel, RBFKernel, TanimotoKernel, diff --git a/bofire/data_models/kernels/continuous.py b/bofire/data_models/kernels/continuous.py index 6c68e6391..9925a411c 100644 --- a/bofire/data_models/kernels/continuous.py +++ b/bofire/data_models/kernels/continuous.py @@ -24,3 +24,9 @@ class MaternKernel(ContinuousKernel): class LinearKernel(ContinuousKernel): type: Literal["LinearKernel"] = "LinearKernel" variance_prior: Optional[AnyPrior] = None + + +class PolynomialKernel(ContinuousKernel): + type: Literal["PolynomialKernel"] = "PolynomialKernel" + offset_prior: Optional[AnyPrior] = None + degree: int = 2 diff --git a/bofire/kernels/mapper.py b/bofire/kernels/mapper.py index 33bd0c9c5..ce95cf042 100644 --- a/bofire/kernels/mapper.py +++ b/bofire/kernels/mapper.py @@ -57,6 +57,20 @@ def map_LinearKernel( ) +def map_PolynomialKernel( + data_model: data_models.PolynomialKernel, + batch_shape: torch.Size, + ard_num_dims: int, + active_dims: List[int], +) -> gpytorch.kernels.PolynomialKernel: + return gpytorch.kernels.PolynomialKernel( + batch_shape=batch_shape, + active_dims=active_dims, + power=data_model.power, + offset_prior=priors.map(data_model.offset_prior), + ) + + def map_AdditiveKernel( data_model: data_models.AdditiveKernel, batch_shape: torch.Size, @@ -131,6 +145,7 @@ def map_TanimotoKernel( data_models.RBFKernel: map_RBFKernel, data_models.MaternKernel: map_MaternKernel, data_models.LinearKernel: map_LinearKernel, + data_models.PolynomialKernel: map_PolynomialKernel, data_models.AdditiveKernel: map_AdditiveKernel, data_models.MultiplicativeKernel: map_MultiplicativeKernel, data_models.ScaleKernel: map_ScaleKernel, diff --git a/tests/bofire/data_models/test_kernels.py b/tests/bofire/data_models/test_kernels.py index 1f7936e76..39984db43 100644 --- a/tests/bofire/data_models/test_kernels.py +++ b/tests/bofire/data_models/test_kernels.py @@ -14,6 +14,7 @@ LinearKernel, MaternKernel, MultiplicativeKernel, + PolynomialKernel, RBFKernel, ScaleKernel, TanimotoKernel, @@ -154,6 +155,26 @@ def test_scale_kernel(): assert hasattr(k, "outputscale_prior") is False +def test_poly_kernel(): + kernel = PolynomialKernel(degree=2, offset_prior=BOTORCH_SCALE_PRIOR()) + k = kernels.map( + kernel, + batch_shape=torch.Size(), + ard_num_dims=10, + active_dims=list(range(5)), + ) + assert hasattr(k, "offset_prior") + assert isinstance(k.offset_prior, gpytorch.priors.GammaPrior) + kernel = PolynomialKernel(degree=2) + k = kernels.map( + kernel, + batch_shape=torch.Size(), + ard_num_dims=10, + active_dims=list(range(5)), + ) + assert hasattr(k, "offset_prior") is False + + @pytest.mark.parametrize( "kernel, ard_num_dims, active_dims, expected_kernel", [ From 342c3bc8cd6ab74b7518b3d070c6a28f42269c7b Mon Sep 17 00:00:00 2001 From: linznedd Date: Thu, 5 Oct 2023 13:37:00 +0200 Subject: [PATCH 02/29] add polynomial kernel --- bofire/data_models/kernels/api.py | 1 + bofire/data_models/kernels/continuous.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/bofire/data_models/kernels/api.py b/bofire/data_models/kernels/api.py index 975be871a..02e4bda76 100644 --- a/bofire/data_models/kernels/api.py +++ b/bofire/data_models/kernels/api.py @@ -25,6 +25,7 @@ MaternKernel, LinearKernel, PolynomialKernel, + PolynomialKernel, RBFKernel, ] diff --git a/bofire/data_models/kernels/continuous.py b/bofire/data_models/kernels/continuous.py index 9925a411c..57a5da643 100644 --- a/bofire/data_models/kernels/continuous.py +++ b/bofire/data_models/kernels/continuous.py @@ -29,4 +29,4 @@ class LinearKernel(ContinuousKernel): class PolynomialKernel(ContinuousKernel): type: Literal["PolynomialKernel"] = "PolynomialKernel" offset_prior: Optional[AnyPrior] = None - degree: int = 2 + power: int = 2 From 300c656f32214bca46a2c42755ddfc2693366177 Mon Sep 17 00:00:00 2001 From: linznedd Date: Thu, 5 Oct 2023 13:45:12 +0200 Subject: [PATCH 03/29] add polynomial kernel --- bofire/kernels/mapper.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bofire/kernels/mapper.py b/bofire/kernels/mapper.py index ce95cf042..91f117636 100644 --- a/bofire/kernels/mapper.py +++ b/bofire/kernels/mapper.py @@ -67,7 +67,9 @@ def map_PolynomialKernel( batch_shape=batch_shape, active_dims=active_dims, power=data_model.power, - offset_prior=priors.map(data_model.offset_prior), + offset_prior=priors.map(data_model.offset_prior) + if data_model.offset_prior is not None + else None, ) From 1092002e42b91c77db8882bce84486b4f5fc9a2b Mon Sep 17 00:00:00 2001 From: linznedd Date: Thu, 5 Oct 2023 13:58:44 +0200 Subject: [PATCH 04/29] add polynomial kernel --- tests/bofire/data_models/test_kernels.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/bofire/data_models/test_kernels.py b/tests/bofire/data_models/test_kernels.py index 39984db43..c2d5d7ed1 100644 --- a/tests/bofire/data_models/test_kernels.py +++ b/tests/bofire/data_models/test_kernels.py @@ -156,7 +156,7 @@ def test_scale_kernel(): def test_poly_kernel(): - kernel = PolynomialKernel(degree=2, offset_prior=BOTORCH_SCALE_PRIOR()) + kernel = PolynomialKernel(power=2, offset_prior=BOTORCH_SCALE_PRIOR()) k = kernels.map( kernel, batch_shape=torch.Size(), @@ -165,7 +165,7 @@ def test_poly_kernel(): ) assert hasattr(k, "offset_prior") assert isinstance(k.offset_prior, gpytorch.priors.GammaPrior) - kernel = PolynomialKernel(degree=2) + kernel = PolynomialKernel(power=2) k = kernels.map( kernel, batch_shape=torch.Size(), From 4ea3a08757528bdea41da76abad2a636f04993ed Mon Sep 17 00:00:00 2001 From: linznedd Date: Thu, 5 Oct 2023 14:23:48 +0200 Subject: [PATCH 05/29] add quadratic surrogate --- bofire/data_models/surrogates/api.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bofire/data_models/surrogates/api.py b/bofire/data_models/surrogates/api.py index 3ba5749e7..eaf44e852 100644 --- a/bofire/data_models/surrogates/api.py +++ b/bofire/data_models/surrogates/api.py @@ -15,6 +15,7 @@ MixedSingleTaskGPSurrogate, ) from bofire.data_models.surrogates.mlp import MLPEnsemble + from bofire.data_models.surrogates.quadratic import QuadraticSurrogate from bofire.data_models.surrogates.random_forest import RandomForestSurrogate from bofire.data_models.surrogates.single_task_gp import ( SingleTaskGPHyperconfig, @@ -36,6 +37,7 @@ SaasSingleTaskGPSurrogate, XGBoostSurrogate, LinearSurrogate, + QuadraticSurrogate, TanimotoGPSurrogate, ] @@ -47,6 +49,7 @@ SaasSingleTaskGPSurrogate, XGBoostSurrogate, LinearSurrogate, + QuadraticSurrogate, TanimotoGPSurrogate, ] except ImportError: From 2a2baecbd3012bd5b034766962f73d0380ac71ea Mon Sep 17 00:00:00 2001 From: linznedd Date: Thu, 5 Oct 2023 14:32:16 +0200 Subject: [PATCH 06/29] adding linear kernel not necessary --- bofire/data_models/kernels/api.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bofire/data_models/kernels/api.py b/bofire/data_models/kernels/api.py index 02e4bda76..975be871a 100644 --- a/bofire/data_models/kernels/api.py +++ b/bofire/data_models/kernels/api.py @@ -25,7 +25,6 @@ MaternKernel, LinearKernel, PolynomialKernel, - PolynomialKernel, RBFKernel, ] From c95f26ca278341b70b0ab6409348499582dd77a9 Mon Sep 17 00:00:00 2001 From: linznedd Date: Thu, 5 Oct 2023 14:52:12 +0200 Subject: [PATCH 07/29] adding linear kernel not necessary --- bofire/data_models/surrogates/quadratic.py | 22 +++++++++++ tests/bofire/surrogates/test_quadratic.py | 43 ++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 bofire/data_models/surrogates/quadratic.py create mode 100644 tests/bofire/surrogates/test_quadratic.py diff --git a/bofire/data_models/surrogates/quadratic.py b/bofire/data_models/surrogates/quadratic.py new file mode 100644 index 000000000..c9291dda6 --- /dev/null +++ b/bofire/data_models/surrogates/quadratic.py @@ -0,0 +1,22 @@ +from typing import Literal + +from pydantic import Field + +from bofire.data_models.kernels.api import ( + LinearKernel, + PolynomialKernel, +) +from bofire.data_models.priors.api import BOTORCH_NOISE_PRIOR, AnyPrior + +# from bofire.data_models.strategies.api import FactorialStrategy +from bofire.data_models.surrogates.botorch import BotorchSurrogate +from bofire.data_models.surrogates.scaler import ScalerEnum +from bofire.data_models.surrogates.trainable import TrainableSurrogate + + +class QuadraticSurrogate(BotorchSurrogate, TrainableSurrogate): + type: Literal["QuadraticSurrogate"] = "QuadraticSurrogate" + + kernel: LinearKernel = Field(default_factory=lambda: PolynomialKernel(power=2)) + noise_prior: AnyPrior = Field(default_factory=lambda: BOTORCH_NOISE_PRIOR()) + scaler: ScalerEnum = ScalerEnum.NORMALIZE diff --git a/tests/bofire/surrogates/test_quadratic.py b/tests/bofire/surrogates/test_quadratic.py new file mode 100644 index 000000000..43cd61362 --- /dev/null +++ b/tests/bofire/surrogates/test_quadratic.py @@ -0,0 +1,43 @@ +import numpy as np +from pandas.testing import assert_frame_equal + +import bofire.surrogates.api as surrogates +from bofire.data_models.domain.api import Inputs, Outputs +from bofire.data_models.features.api import ContinuousInput, ContinuousOutput +from bofire.data_models.kernels.api import PolynomialKernel +from bofire.data_models.surrogates.api import QuadraticSurrogate + + +def test_QuadraticSurrogate(): + N_EXPERIMENTS = 10 + + inputs = Inputs( + features=[ + ContinuousInput(key="a", bounds=(0, 40)), + ContinuousInput(key="b", bounds=(20, 60)), + ] + ) + outputs = Outputs(features=[ContinuousOutput(key="c")]) + + experiments = inputs.sample(N_EXPERIMENTS) + experiments["c"] = ( + experiments["a"] * 2.2 + + experiments["b"] * -0.05 + + experiments["b"] + + np.random.normal(loc=0, scale=5, size=N_EXPERIMENTS) + ) + experiments["valid_c"] = 1 + + surrogate_data = QuadraticSurrogate(inputs=inputs, outputs=outputs) + surrogate = surrogates.map(surrogate_data) + + assert isinstance(surrogate, surrogates.SingleTaskGPSurrogate) + assert isinstance(surrogate.kernel, PolynomialKernel(power=2)) + + # check dump + surrogate.fit(experiments=experiments) + preds = surrogate.predict(experiments) + dump = surrogate.dumps() + surrogate.loads(dump) + preds2 = surrogate.predict(experiments) + assert_frame_equal(preds, preds2) From 6e0fbc921fc0fc87f263d747c26d1b001528508a Mon Sep 17 00:00:00 2001 From: linznedd Date: Thu, 5 Oct 2023 15:11:10 +0200 Subject: [PATCH 08/29] adding linear kernel not necessary --- bofire/surrogates/mapper.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bofire/surrogates/mapper.py b/bofire/surrogates/mapper.py index 5be8d4f3e..88c7616f1 100644 --- a/bofire/surrogates/mapper.py +++ b/bofire/surrogates/mapper.py @@ -19,6 +19,7 @@ data_models.SaasSingleTaskGPSurrogate: SaasSingleTaskGPSurrogate, data_models.XGBoostSurrogate: XGBoostSurrogate, data_models.LinearSurrogate: SingleTaskGPSurrogate, + data_models.QuadraticSurrogate: SingleTaskGPSurrogate, data_models.TanimotoGPSurrogate: SingleTaskGPSurrogate, } From 26be3f6473f95d6332bf41309f75d00f6117c687 Mon Sep 17 00:00:00 2001 From: linznedd Date: Thu, 5 Oct 2023 15:23:04 +0200 Subject: [PATCH 09/29] fix test --- tests/bofire/surrogates/test_quadratic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/bofire/surrogates/test_quadratic.py b/tests/bofire/surrogates/test_quadratic.py index 43cd61362..149c4989d 100644 --- a/tests/bofire/surrogates/test_quadratic.py +++ b/tests/bofire/surrogates/test_quadratic.py @@ -32,7 +32,7 @@ def test_QuadraticSurrogate(): surrogate = surrogates.map(surrogate_data) assert isinstance(surrogate, surrogates.SingleTaskGPSurrogate) - assert isinstance(surrogate.kernel, PolynomialKernel(power=2)) + assert isinstance(surrogate.kernel, PolynomialKernel) # check dump surrogate.fit(experiments=experiments) From dab5f7f51111376871dee83360dc48d5ec6a2a4b Mon Sep 17 00:00:00 2001 From: linznedd Date: Thu, 5 Oct 2023 15:42:27 +0200 Subject: [PATCH 10/29] change kernel tzpe hint --- bofire/data_models/surrogates/quadratic.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bofire/data_models/surrogates/quadratic.py b/bofire/data_models/surrogates/quadratic.py index c9291dda6..8677acfd0 100644 --- a/bofire/data_models/surrogates/quadratic.py +++ b/bofire/data_models/surrogates/quadratic.py @@ -3,7 +3,6 @@ from pydantic import Field from bofire.data_models.kernels.api import ( - LinearKernel, PolynomialKernel, ) from bofire.data_models.priors.api import BOTORCH_NOISE_PRIOR, AnyPrior @@ -17,6 +16,6 @@ class QuadraticSurrogate(BotorchSurrogate, TrainableSurrogate): type: Literal["QuadraticSurrogate"] = "QuadraticSurrogate" - kernel: LinearKernel = Field(default_factory=lambda: PolynomialKernel(power=2)) + kernel: PolynomialKernel = Field(default_factory=lambda: PolynomialKernel(power=2)) noise_prior: AnyPrior = Field(default_factory=lambda: BOTORCH_NOISE_PRIOR()) scaler: ScalerEnum = ScalerEnum.NORMALIZE From a7bf562b3991e8f94409df8c80224d0bde5ce0a4 Mon Sep 17 00:00:00 2001 From: linznedd Date: Fri, 6 Oct 2023 14:58:16 +0200 Subject: [PATCH 11/29] generalize quadratic surrogate --- bofire/data_models/surrogates/api.py | 6 +-- bofire/data_models/surrogates/quadratic.py | 21 ----------- bofire/surrogates/mapper.py | 2 +- tests/bofire/surrogates/test_quadratic.py | 43 ---------------------- 4 files changed, 4 insertions(+), 68 deletions(-) delete mode 100644 bofire/data_models/surrogates/quadratic.py delete mode 100644 tests/bofire/surrogates/test_quadratic.py diff --git a/bofire/data_models/surrogates/api.py b/bofire/data_models/surrogates/api.py index eaf44e852..cf8109636 100644 --- a/bofire/data_models/surrogates/api.py +++ b/bofire/data_models/surrogates/api.py @@ -15,7 +15,7 @@ MixedSingleTaskGPSurrogate, ) from bofire.data_models.surrogates.mlp import MLPEnsemble - from bofire.data_models.surrogates.quadratic import QuadraticSurrogate + from bofire.data_models.surrogates.polynomial import PolynomialSurrogate from bofire.data_models.surrogates.random_forest import RandomForestSurrogate from bofire.data_models.surrogates.single_task_gp import ( SingleTaskGPHyperconfig, @@ -37,7 +37,7 @@ SaasSingleTaskGPSurrogate, XGBoostSurrogate, LinearSurrogate, - QuadraticSurrogate, + PolynomialSurrogate, TanimotoGPSurrogate, ] @@ -49,7 +49,7 @@ SaasSingleTaskGPSurrogate, XGBoostSurrogate, LinearSurrogate, - QuadraticSurrogate, + PolynomialSurrogate, TanimotoGPSurrogate, ] except ImportError: diff --git a/bofire/data_models/surrogates/quadratic.py b/bofire/data_models/surrogates/quadratic.py deleted file mode 100644 index 8677acfd0..000000000 --- a/bofire/data_models/surrogates/quadratic.py +++ /dev/null @@ -1,21 +0,0 @@ -from typing import Literal - -from pydantic import Field - -from bofire.data_models.kernels.api import ( - PolynomialKernel, -) -from bofire.data_models.priors.api import BOTORCH_NOISE_PRIOR, AnyPrior - -# from bofire.data_models.strategies.api import FactorialStrategy -from bofire.data_models.surrogates.botorch import BotorchSurrogate -from bofire.data_models.surrogates.scaler import ScalerEnum -from bofire.data_models.surrogates.trainable import TrainableSurrogate - - -class QuadraticSurrogate(BotorchSurrogate, TrainableSurrogate): - type: Literal["QuadraticSurrogate"] = "QuadraticSurrogate" - - kernel: PolynomialKernel = Field(default_factory=lambda: PolynomialKernel(power=2)) - noise_prior: AnyPrior = Field(default_factory=lambda: BOTORCH_NOISE_PRIOR()) - scaler: ScalerEnum = ScalerEnum.NORMALIZE diff --git a/bofire/surrogates/mapper.py b/bofire/surrogates/mapper.py index 88c7616f1..a4f588085 100644 --- a/bofire/surrogates/mapper.py +++ b/bofire/surrogates/mapper.py @@ -19,7 +19,7 @@ data_models.SaasSingleTaskGPSurrogate: SaasSingleTaskGPSurrogate, data_models.XGBoostSurrogate: XGBoostSurrogate, data_models.LinearSurrogate: SingleTaskGPSurrogate, - data_models.QuadraticSurrogate: SingleTaskGPSurrogate, + data_models.PolynomialSurrogate: SingleTaskGPSurrogate, data_models.TanimotoGPSurrogate: SingleTaskGPSurrogate, } diff --git a/tests/bofire/surrogates/test_quadratic.py b/tests/bofire/surrogates/test_quadratic.py deleted file mode 100644 index 149c4989d..000000000 --- a/tests/bofire/surrogates/test_quadratic.py +++ /dev/null @@ -1,43 +0,0 @@ -import numpy as np -from pandas.testing import assert_frame_equal - -import bofire.surrogates.api as surrogates -from bofire.data_models.domain.api import Inputs, Outputs -from bofire.data_models.features.api import ContinuousInput, ContinuousOutput -from bofire.data_models.kernels.api import PolynomialKernel -from bofire.data_models.surrogates.api import QuadraticSurrogate - - -def test_QuadraticSurrogate(): - N_EXPERIMENTS = 10 - - inputs = Inputs( - features=[ - ContinuousInput(key="a", bounds=(0, 40)), - ContinuousInput(key="b", bounds=(20, 60)), - ] - ) - outputs = Outputs(features=[ContinuousOutput(key="c")]) - - experiments = inputs.sample(N_EXPERIMENTS) - experiments["c"] = ( - experiments["a"] * 2.2 - + experiments["b"] * -0.05 - + experiments["b"] - + np.random.normal(loc=0, scale=5, size=N_EXPERIMENTS) - ) - experiments["valid_c"] = 1 - - surrogate_data = QuadraticSurrogate(inputs=inputs, outputs=outputs) - surrogate = surrogates.map(surrogate_data) - - assert isinstance(surrogate, surrogates.SingleTaskGPSurrogate) - assert isinstance(surrogate.kernel, PolynomialKernel) - - # check dump - surrogate.fit(experiments=experiments) - preds = surrogate.predict(experiments) - dump = surrogate.dumps() - surrogate.loads(dump) - preds2 = surrogate.predict(experiments) - assert_frame_equal(preds, preds2) From 62d79089202ea3d836667e0bba2bc83b625a03c8 Mon Sep 17 00:00:00 2001 From: linznedd Date: Fri, 6 Oct 2023 15:01:42 +0200 Subject: [PATCH 12/29] generalize quadratic surrogate --- bofire/data_models/surrogates/polynomial.py | 24 ++++++++++++ tests/bofire/surrogates/test_polynomial.py | 43 +++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 bofire/data_models/surrogates/polynomial.py create mode 100644 tests/bofire/surrogates/test_polynomial.py diff --git a/bofire/data_models/surrogates/polynomial.py b/bofire/data_models/surrogates/polynomial.py new file mode 100644 index 000000000..5503db11d --- /dev/null +++ b/bofire/data_models/surrogates/polynomial.py @@ -0,0 +1,24 @@ +from typing import Literal + +from pydantic import Field + +from bofire.data_models.kernels.api import ( + PolynomialKernel, +) +from bofire.data_models.priors.api import BOTORCH_NOISE_PRIOR, AnyPrior + +# from bofire.data_models.strategies.api import FactorialStrategy +from bofire.data_models.surrogates.botorch import BotorchSurrogate +from bofire.data_models.surrogates.scaler import ScalerEnum +from bofire.data_models.surrogates.trainable import TrainableSurrogate + + +class PolynomialSurrogate(BotorchSurrogate, TrainableSurrogate): + type: Literal["PolynomialSurrogate"] = "PolynomialSurrogate" + noise_prior: AnyPrior = Field(default_factory=lambda: BOTORCH_NOISE_PRIOR()) + scaler: ScalerEnum = ScalerEnum.NORMALIZE + + def __init__(self, power=2): + self.kernel: PolynomialKernel = Field( + default_factory=lambda: PolynomialKernel(power=power) + ) diff --git a/tests/bofire/surrogates/test_polynomial.py b/tests/bofire/surrogates/test_polynomial.py new file mode 100644 index 000000000..f6b504cf4 --- /dev/null +++ b/tests/bofire/surrogates/test_polynomial.py @@ -0,0 +1,43 @@ +import numpy as np +from pandas.testing import assert_frame_equal + +import bofire.surrogates.api as surrogates +from bofire.data_models.domain.api import Inputs, Outputs +from bofire.data_models.features.api import ContinuousInput, ContinuousOutput +from bofire.data_models.kernels.api import PolynomialKernel +from bofire.data_models.surrogates.api import PolynomialSurrogate + + +def test_QuadraticSurrogate(): + N_EXPERIMENTS = 10 + + inputs = Inputs( + features=[ + ContinuousInput(key="a", bounds=(0, 40)), + ContinuousInput(key="b", bounds=(20, 60)), + ] + ) + outputs = Outputs(features=[ContinuousOutput(key="c")]) + + experiments = inputs.sample(N_EXPERIMENTS) + experiments["c"] = ( + experiments["a"] * 2.2 + + experiments["b"] * -0.05 + + experiments["b"] + + np.random.normal(loc=0, scale=5, size=N_EXPERIMENTS) + ) + experiments["valid_c"] = 1 + + surrogate_data = PolynomialSurrogate(inputs=inputs, outputs=outputs, power=2) + surrogate = surrogates.map(surrogate_data) + + assert isinstance(surrogate, surrogates.SingleTaskGPSurrogate) + assert isinstance(surrogate.kernel, PolynomialKernel) + + # check dump + surrogate.fit(experiments=experiments) + preds = surrogate.predict(experiments) + dump = surrogate.dumps() + surrogate.loads(dump) + preds2 = surrogate.predict(experiments) + assert_frame_equal(preds, preds2) From ebbff71e98234d8cd113fa32369698c8981aea2f Mon Sep 17 00:00:00 2001 From: linznedd Date: Fri, 6 Oct 2023 15:19:47 +0200 Subject: [PATCH 13/29] generalize quadratic surrogate --- bofire/data_models/surrogates/quadratic.py | 21 ----------- tests/bofire/surrogates/test_quadratic.py | 43 ---------------------- 2 files changed, 64 deletions(-) delete mode 100644 bofire/data_models/surrogates/quadratic.py delete mode 100644 tests/bofire/surrogates/test_quadratic.py diff --git a/bofire/data_models/surrogates/quadratic.py b/bofire/data_models/surrogates/quadratic.py deleted file mode 100644 index 8677acfd0..000000000 --- a/bofire/data_models/surrogates/quadratic.py +++ /dev/null @@ -1,21 +0,0 @@ -from typing import Literal - -from pydantic import Field - -from bofire.data_models.kernels.api import ( - PolynomialKernel, -) -from bofire.data_models.priors.api import BOTORCH_NOISE_PRIOR, AnyPrior - -# from bofire.data_models.strategies.api import FactorialStrategy -from bofire.data_models.surrogates.botorch import BotorchSurrogate -from bofire.data_models.surrogates.scaler import ScalerEnum -from bofire.data_models.surrogates.trainable import TrainableSurrogate - - -class QuadraticSurrogate(BotorchSurrogate, TrainableSurrogate): - type: Literal["QuadraticSurrogate"] = "QuadraticSurrogate" - - kernel: PolynomialKernel = Field(default_factory=lambda: PolynomialKernel(power=2)) - noise_prior: AnyPrior = Field(default_factory=lambda: BOTORCH_NOISE_PRIOR()) - scaler: ScalerEnum = ScalerEnum.NORMALIZE diff --git a/tests/bofire/surrogates/test_quadratic.py b/tests/bofire/surrogates/test_quadratic.py deleted file mode 100644 index 149c4989d..000000000 --- a/tests/bofire/surrogates/test_quadratic.py +++ /dev/null @@ -1,43 +0,0 @@ -import numpy as np -from pandas.testing import assert_frame_equal - -import bofire.surrogates.api as surrogates -from bofire.data_models.domain.api import Inputs, Outputs -from bofire.data_models.features.api import ContinuousInput, ContinuousOutput -from bofire.data_models.kernels.api import PolynomialKernel -from bofire.data_models.surrogates.api import QuadraticSurrogate - - -def test_QuadraticSurrogate(): - N_EXPERIMENTS = 10 - - inputs = Inputs( - features=[ - ContinuousInput(key="a", bounds=(0, 40)), - ContinuousInput(key="b", bounds=(20, 60)), - ] - ) - outputs = Outputs(features=[ContinuousOutput(key="c")]) - - experiments = inputs.sample(N_EXPERIMENTS) - experiments["c"] = ( - experiments["a"] * 2.2 - + experiments["b"] * -0.05 - + experiments["b"] - + np.random.normal(loc=0, scale=5, size=N_EXPERIMENTS) - ) - experiments["valid_c"] = 1 - - surrogate_data = QuadraticSurrogate(inputs=inputs, outputs=outputs) - surrogate = surrogates.map(surrogate_data) - - assert isinstance(surrogate, surrogates.SingleTaskGPSurrogate) - assert isinstance(surrogate.kernel, PolynomialKernel) - - # check dump - surrogate.fit(experiments=experiments) - preds = surrogate.predict(experiments) - dump = surrogate.dumps() - surrogate.loads(dump) - preds2 = surrogate.predict(experiments) - assert_frame_equal(preds, preds2) From 081ab8fe9a78d49cce4e9179dab268e8e4037e6c Mon Sep 17 00:00:00 2001 From: linznedd Date: Fri, 6 Oct 2023 15:35:25 +0200 Subject: [PATCH 14/29] fix init --- bofire/data_models/surrogates/polynomial.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/bofire/data_models/surrogates/polynomial.py b/bofire/data_models/surrogates/polynomial.py index 5503db11d..4c812a179 100644 --- a/bofire/data_models/surrogates/polynomial.py +++ b/bofire/data_models/surrogates/polynomial.py @@ -2,6 +2,8 @@ from pydantic import Field +from bofire.data_models.domain.api import Inputs, Outputs +from bofire.data_models.features.api import TInputTransformSpecs from bofire.data_models.kernels.api import ( PolynomialKernel, ) @@ -18,7 +20,22 @@ class PolynomialSurrogate(BotorchSurrogate, TrainableSurrogate): noise_prior: AnyPrior = Field(default_factory=lambda: BOTORCH_NOISE_PRIOR()) scaler: ScalerEnum = ScalerEnum.NORMALIZE - def __init__(self, power=2): + def __init__( + self, + type: str, + inputs: Inputs, + outputs: Outputs, + power: int, + input_preprocessing_specs: TInputTransformSpecs = dict, + dump: str | None = None, + ): + super().__init__( + type=type, + inputs=inputs, + outputs=outputs, + input_preprocessing_specs=input_preprocessing_specs, + dump=dump, + ) self.kernel: PolynomialKernel = Field( default_factory=lambda: PolynomialKernel(power=power) ) From a0551b75733958696a61d043576d508f9dbd816f Mon Sep 17 00:00:00 2001 From: linznedd Date: Fri, 6 Oct 2023 15:38:23 +0200 Subject: [PATCH 15/29] fix init --- bofire/data_models/surrogates/polynomial.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/bofire/data_models/surrogates/polynomial.py b/bofire/data_models/surrogates/polynomial.py index 4c812a179..028c694b7 100644 --- a/bofire/data_models/surrogates/polynomial.py +++ b/bofire/data_models/surrogates/polynomial.py @@ -3,7 +3,6 @@ from pydantic import Field from bofire.data_models.domain.api import Inputs, Outputs -from bofire.data_models.features.api import TInputTransformSpecs from bofire.data_models.kernels.api import ( PolynomialKernel, ) @@ -26,15 +25,11 @@ def __init__( inputs: Inputs, outputs: Outputs, power: int, - input_preprocessing_specs: TInputTransformSpecs = dict, - dump: str | None = None, ): super().__init__( type=type, inputs=inputs, outputs=outputs, - input_preprocessing_specs=input_preprocessing_specs, - dump=dump, ) self.kernel: PolynomialKernel = Field( default_factory=lambda: PolynomialKernel(power=power) From 5bbe12ee89d4d1af6e96df633f609b712d473c53 Mon Sep 17 00:00:00 2001 From: linznedd Date: Fri, 6 Oct 2023 15:50:50 +0200 Subject: [PATCH 16/29] fix init --- bofire/data_models/surrogates/polynomial.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/bofire/data_models/surrogates/polynomial.py b/bofire/data_models/surrogates/polynomial.py index 028c694b7..b2c7a72bc 100644 --- a/bofire/data_models/surrogates/polynomial.py +++ b/bofire/data_models/surrogates/polynomial.py @@ -21,13 +21,11 @@ class PolynomialSurrogate(BotorchSurrogate, TrainableSurrogate): def __init__( self, - type: str, inputs: Inputs, outputs: Outputs, power: int, ): super().__init__( - type=type, inputs=inputs, outputs=outputs, ) From 2ce616ea14cf5180f12bbcd8aefa9c8b5e676fc8 Mon Sep 17 00:00:00 2001 From: linznedd Date: Fri, 6 Oct 2023 15:51:27 +0200 Subject: [PATCH 17/29] fix init --- tests/bofire/surrogates/test_polynomial.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/bofire/surrogates/test_polynomial.py b/tests/bofire/surrogates/test_polynomial.py index f6b504cf4..1a3557c35 100644 --- a/tests/bofire/surrogates/test_polynomial.py +++ b/tests/bofire/surrogates/test_polynomial.py @@ -8,7 +8,7 @@ from bofire.data_models.surrogates.api import PolynomialSurrogate -def test_QuadraticSurrogate(): +def test_polynomial_surrogate(): N_EXPERIMENTS = 10 inputs = Inputs( From 4a69e53337951dedcf1219e81ec8cd3e415264b6 Mon Sep 17 00:00:00 2001 From: linznedd Date: Mon, 9 Oct 2023 15:39:47 +0200 Subject: [PATCH 18/29] fix init --- bofire/data_models/surrogates/polynomial.py | 18 ++++++------------ tests/bofire/surrogates/test_polynomial.py | 4 +++- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/bofire/data_models/surrogates/polynomial.py b/bofire/data_models/surrogates/polynomial.py index b2c7a72bc..bd2225374 100644 --- a/bofire/data_models/surrogates/polynomial.py +++ b/bofire/data_models/surrogates/polynomial.py @@ -16,19 +16,13 @@ class PolynomialSurrogate(BotorchSurrogate, TrainableSurrogate): type: Literal["PolynomialSurrogate"] = "PolynomialSurrogate" + + kernel: PolynomialKernel = Field(default_factory=lambda: PolynomialKernel(power=2)) noise_prior: AnyPrior = Field(default_factory=lambda: BOTORCH_NOISE_PRIOR()) scaler: ScalerEnum = ScalerEnum.NORMALIZE - def __init__( - self, - inputs: Inputs, - outputs: Outputs, - power: int, - ): - super().__init__( - inputs=inputs, - outputs=outputs, - ) - self.kernel: PolynomialKernel = Field( - default_factory=lambda: PolynomialKernel(power=power) + @staticmethod + def from_power(power: int, inputs: Inputs, outputs: Outputs): + return PolynomialSurrogate( + kernel=PolynomialKernel(power=power), inputs=inputs, outputs=outputs ) diff --git a/tests/bofire/surrogates/test_polynomial.py b/tests/bofire/surrogates/test_polynomial.py index 1a3557c35..62fb3238f 100644 --- a/tests/bofire/surrogates/test_polynomial.py +++ b/tests/bofire/surrogates/test_polynomial.py @@ -28,7 +28,9 @@ def test_polynomial_surrogate(): ) experiments["valid_c"] = 1 - surrogate_data = PolynomialSurrogate(inputs=inputs, outputs=outputs, power=2) + surrogate_data = PolynomialSurrogate.from_power( + power=2, inputs=inputs, outputs=outputs + ) surrogate = surrogates.map(surrogate_data) assert isinstance(surrogate, surrogates.SingleTaskGPSurrogate) From 34aa157668ae4b48ca397858967d1a5f2b2b89e8 Mon Sep 17 00:00:00 2001 From: linznedd Date: Tue, 10 Oct 2023 16:06:54 +0200 Subject: [PATCH 19/29] add polynomial_surrogate to AnyBotochSurrogate --- bofire/data_models/surrogates/botorch_surrogates.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bofire/data_models/surrogates/botorch_surrogates.py b/bofire/data_models/surrogates/botorch_surrogates.py index b78f6c778..44bdc6b72 100644 --- a/bofire/data_models/surrogates/botorch_surrogates.py +++ b/bofire/data_models/surrogates/botorch_surrogates.py @@ -13,6 +13,7 @@ MixedSingleTaskGPSurrogate, ) from bofire.data_models.surrogates.mlp import MLPEnsemble +from bofire.data_models.surrogates.polynomial import PolynomialSurrogate from bofire.data_models.surrogates.random_forest import RandomForestSurrogate from bofire.data_models.surrogates.single_task_gp import SingleTaskGPSurrogate from bofire.data_models.surrogates.tanimoto_gp import TanimotoGPSurrogate @@ -26,6 +27,7 @@ SaasSingleTaskGPSurrogate, TanimotoGPSurrogate, LinearSurrogate, + PolynomialSurrogate, ] From 01f5d213fa5c0a1bc71abf6ced642149104a51e3 Mon Sep 17 00:00:00 2001 From: linznedd Date: Tue, 10 Oct 2023 16:21:05 +0200 Subject: [PATCH 20/29] add test that botorch surrogate can be composed --- tests/bofire/surrogates/test_linear.py | 21 +++++++++++++++++- tests/bofire/surrogates/test_polynomial.py | 25 +++++++++++++++++++++- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/tests/bofire/surrogates/test_linear.py b/tests/bofire/surrogates/test_linear.py index fe6fbb180..7507b4136 100644 --- a/tests/bofire/surrogates/test_linear.py +++ b/tests/bofire/surrogates/test_linear.py @@ -5,7 +5,7 @@ from bofire.data_models.domain.api import Inputs, Outputs from bofire.data_models.features.api import ContinuousInput, ContinuousOutput from bofire.data_models.kernels.api import LinearKernel -from bofire.data_models.surrogates.api import LinearSurrogate +from bofire.data_models.surrogates.api import BotorchSurrogates, LinearSurrogate def test_LinearSurrogate(): @@ -41,3 +41,22 @@ def test_LinearSurrogate(): surrogate.loads(dump) preds2 = surrogate.predict(experiments) assert_frame_equal(preds, preds2) + + +def test_can_define_botorch_surrogate(): + inputs = Inputs( + features=[ + ContinuousInput(key="a", bounds=(0, 40)), + ContinuousInput(key="b", bounds=(20, 60)), + ] + ) + outputs = Outputs(features=[ContinuousOutput(key="c"), ContinuousOutput(key="d")]) + _ = ( + BotorchSurrogates( + surrogates=[ + LinearSurrogate(inputs=inputs, outputs=Outputs(features=[outputs[0]])), + LinearSurrogate(inputs=inputs, outputs=Outputs(features=[outputs[1]])), + ] + ), + ) + assert True diff --git a/tests/bofire/surrogates/test_polynomial.py b/tests/bofire/surrogates/test_polynomial.py index 62fb3238f..b991ed2ea 100644 --- a/tests/bofire/surrogates/test_polynomial.py +++ b/tests/bofire/surrogates/test_polynomial.py @@ -5,7 +5,7 @@ from bofire.data_models.domain.api import Inputs, Outputs from bofire.data_models.features.api import ContinuousInput, ContinuousOutput from bofire.data_models.kernels.api import PolynomialKernel -from bofire.data_models.surrogates.api import PolynomialSurrogate +from bofire.data_models.surrogates.api import BotorchSurrogates, PolynomialSurrogate def test_polynomial_surrogate(): @@ -43,3 +43,26 @@ def test_polynomial_surrogate(): surrogate.loads(dump) preds2 = surrogate.predict(experiments) assert_frame_equal(preds, preds2) + + +def test_can_define_botorch_surrogate(): + inputs = Inputs( + features=[ + ContinuousInput(key="a", bounds=(0, 40)), + ContinuousInput(key="b", bounds=(20, 60)), + ] + ) + outputs = Outputs(features=[ContinuousOutput(key="c"), ContinuousOutput(key="d")]) + _ = ( + BotorchSurrogates( + surrogates=[ + PolynomialSurrogate( + inputs=inputs, outputs=Outputs(features=[outputs[0]]) + ), + PolynomialSurrogate( + inputs=inputs, outputs=Outputs(features=[outputs[1]]) + ), + ] + ), + ) + assert True From 8d1351cb13f70dfcd1f0f631594e2677e6e42c15 Mon Sep 17 00:00:00 2001 From: linznedd Date: Tue, 10 Oct 2023 16:22:25 +0200 Subject: [PATCH 21/29] add test that botorch surrogate can be composed --- tests/bofire/surrogates/test_linear.py | 2 +- tests/bofire/surrogates/test_polynomial.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/bofire/surrogates/test_linear.py b/tests/bofire/surrogates/test_linear.py index 7507b4136..411b238aa 100644 --- a/tests/bofire/surrogates/test_linear.py +++ b/tests/bofire/surrogates/test_linear.py @@ -50,7 +50,7 @@ def test_can_define_botorch_surrogate(): ContinuousInput(key="b", bounds=(20, 60)), ] ) - outputs = Outputs(features=[ContinuousOutput(key="c"), ContinuousOutput(key="d")]) + outputs = [ContinuousOutput(key="c"), ContinuousOutput(key="d")] _ = ( BotorchSurrogates( surrogates=[ diff --git a/tests/bofire/surrogates/test_polynomial.py b/tests/bofire/surrogates/test_polynomial.py index b991ed2ea..60bca638e 100644 --- a/tests/bofire/surrogates/test_polynomial.py +++ b/tests/bofire/surrogates/test_polynomial.py @@ -52,7 +52,7 @@ def test_can_define_botorch_surrogate(): ContinuousInput(key="b", bounds=(20, 60)), ] ) - outputs = Outputs(features=[ContinuousOutput(key="c"), ContinuousOutput(key="d")]) + outputs = [ContinuousOutput(key="c"), ContinuousOutput(key="d")] _ = ( BotorchSurrogates( surrogates=[ From 93320de8f007dab51f102f7ae2f16bc8efb7eb85 Mon Sep 17 00:00:00 2001 From: linznedd Date: Tue, 10 Oct 2023 17:33:14 +0200 Subject: [PATCH 22/29] add test that botorch surrogate can be composed --- tests/bofire/surrogates/test_linear.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/bofire/surrogates/test_linear.py b/tests/bofire/surrogates/test_linear.py index 411b238aa..bb1f611ee 100644 --- a/tests/bofire/surrogates/test_linear.py +++ b/tests/bofire/surrogates/test_linear.py @@ -47,7 +47,7 @@ def test_can_define_botorch_surrogate(): inputs = Inputs( features=[ ContinuousInput(key="a", bounds=(0, 40)), - ContinuousInput(key="b", bounds=(20, 60)), + ContinuousInput(key="b", bounds=(20, 80)), ] ) outputs = [ContinuousOutput(key="c"), ContinuousOutput(key="d")] From 12a7c0473be916d2773a7afc72a7640178ce4483 Mon Sep 17 00:00:00 2001 From: linznedd Date: Wed, 11 Oct 2023 09:03:55 +0200 Subject: [PATCH 23/29] add test that botorch surrogate can be composed --- tests/bofire/surrogates/test_polynomial.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/bofire/surrogates/test_polynomial.py b/tests/bofire/surrogates/test_polynomial.py index f4acb3022..6e255913b 100644 --- a/tests/bofire/surrogates/test_polynomial.py +++ b/tests/bofire/surrogates/test_polynomial.py @@ -44,12 +44,12 @@ def test_polynomial_surrogate(): preds2 = surrogate.predict(experiments) assert_frame_equal(preds, preds2) - + def test_can_define_botorch_surrogate(): inputs = Inputs( features=[ ContinuousInput(key="a", bounds=(0, 40)), - ContinuousInput(key="b", bounds=(20, 60)), + ContinuousInput(key="b", bounds=(20, 80)), ] ) outputs = [ContinuousOutput(key="c"), ContinuousOutput(key="d")] @@ -66,4 +66,3 @@ def test_can_define_botorch_surrogate(): ), ) assert True - From ebb41fc0a0b2906d04ed1fbaa8ee92f6ff0da498 Mon Sep 17 00:00:00 2001 From: linznedd Date: Thu, 12 Oct 2023 08:32:08 +0200 Subject: [PATCH 24/29] add test that botorch surrogate can be composed --- tests/bofire/surrogates/test_linear.py | 3 +-- tests/bofire/surrogates/test_polynomial.py | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/bofire/surrogates/test_linear.py b/tests/bofire/surrogates/test_linear.py index bb1f611ee..d9549e328 100644 --- a/tests/bofire/surrogates/test_linear.py +++ b/tests/bofire/surrogates/test_linear.py @@ -51,7 +51,7 @@ def test_can_define_botorch_surrogate(): ] ) outputs = [ContinuousOutput(key="c"), ContinuousOutput(key="d")] - _ = ( + ( BotorchSurrogates( surrogates=[ LinearSurrogate(inputs=inputs, outputs=Outputs(features=[outputs[0]])), @@ -59,4 +59,3 @@ def test_can_define_botorch_surrogate(): ] ), ) - assert True diff --git a/tests/bofire/surrogates/test_polynomial.py b/tests/bofire/surrogates/test_polynomial.py index 6e255913b..c8264b9ab 100644 --- a/tests/bofire/surrogates/test_polynomial.py +++ b/tests/bofire/surrogates/test_polynomial.py @@ -53,7 +53,7 @@ def test_can_define_botorch_surrogate(): ] ) outputs = [ContinuousOutput(key="c"), ContinuousOutput(key="d")] - _ = ( + ( BotorchSurrogates( surrogates=[ PolynomialSurrogate( @@ -65,4 +65,3 @@ def test_can_define_botorch_surrogate(): ] ), ) - assert True From 9c235b3a8a21df7d14915dd72c994111ed168bb2 Mon Sep 17 00:00:00 2001 From: linznedd Date: Fri, 20 Oct 2023 11:33:56 +0200 Subject: [PATCH 25/29] added test for iterative strategy --- tests/bofire/strategies/test_doe.py | 619 +++++++++++++++------------- 1 file changed, 325 insertions(+), 294 deletions(-) diff --git a/tests/bofire/strategies/test_doe.py b/tests/bofire/strategies/test_doe.py index efb9c5819..64652721e 100644 --- a/tests/bofire/strategies/test_doe.py +++ b/tests/bofire/strategies/test_doe.py @@ -1,294 +1,325 @@ -import warnings - -import numpy as np -import pandas as pd - -import bofire.data_models.strategies.api as data_models -from bofire.data_models.constraints.api import ( - LinearEqualityConstraint, - LinearInequalityConstraint, - NChooseKConstraint, -) -from bofire.data_models.domain.api import Domain -from bofire.data_models.features.api import ( - CategoricalInput, - ContinuousInput, - ContinuousOutput, - DiscreteInput, -) -from bofire.strategies.api import DoEStrategy - -# from tests.bofire.strategies.botorch.test_model_spec import VALID_MODEL_SPEC_LIST - -warnings.filterwarnings("ignore", category=DeprecationWarning) -warnings.filterwarnings("ignore", category=UserWarning, append=True) - -with warnings.catch_warnings(): - warnings.simplefilter("ignore") - -inputs = [ - ContinuousInput( - key=f"x{1}", - bounds=(0.0, 1.0), - ), - ContinuousInput( - key=f"x{2}", - bounds=(0.1, 1.0), - ), - ContinuousInput( - key=f"x{3}", - bounds=(0.0, 0.6), - ), -] -domain = Domain.from_lists( - inputs=inputs, - outputs=[ContinuousOutput(key="y")], - constraints=[ - LinearEqualityConstraint( - features=[f"x{i + 1}" for i in range(3)], coefficients=[1, 1, 1], rhs=1 - ), - LinearInequalityConstraint(features=["x1", "x2"], coefficients=[5, 4], rhs=3.9), - LinearInequalityConstraint( - features=["x1", "x2"], coefficients=[-20, 5], rhs=-3 - ), - ], -) - - -def test_doe_strategy_init(): - data_model = data_models.DoEStrategy(domain=domain, formula="linear") - strategy = DoEStrategy(data_model=data_model) - assert strategy is not None - - -def test_doe_strategy_ask(): - data_model = data_models.DoEStrategy(domain=domain, formula="linear") - strategy = DoEStrategy(data_model=data_model) - candidates = strategy.ask(candidate_count=12) - assert candidates.shape == (12, 3) - - -def test_doe_strategy_ask_with_candidates(): - candidates_fixed = pd.DataFrame( - np.array([[0.2, 0.2, 0.6], [0.3, 0.6, 0.1], [0.7, 0.1, 0.2], [0.3, 0.1, 0.6]]), - columns=["x1", "x2", "x3"], - ) - data_model = data_models.DoEStrategy(domain=domain, formula="linear") - strategy = DoEStrategy(data_model=data_model) - strategy.set_candidates(candidates_fixed) - candidates = strategy.ask(candidate_count=12) - assert candidates.shape == (12, 3) - - -def test_nchoosek_implemented(): - nchoosek_constraint = NChooseKConstraint( - features=[f"x{i + 1}" for i in range(3)], - min_count=0, - max_count=2, - none_also_valid=True, - ) - domain = Domain.from_lists( - inputs=[ContinuousInput(key=f"x{i + 1}", bounds=(0.0, 1.0)) for i in range(3)], - outputs=[ContinuousOutput(key="y")], - constraints=[nchoosek_constraint], - ) - data_model = data_models.DoEStrategy( - domain=domain, formula="linear", optimization_strategy="partially-random" - ) - strategy = DoEStrategy(data_model=data_model) - candidates = strategy.ask(candidate_count=12) - assert candidates.shape == (12, 3) - - -def test_formulas_implemented(): - expected_num_candidates = { - "linear": 7, # 1+a+b+c+3 - "linear-and-quadratic": 10, # 1+a+b+c+a**2+b**2+c**2+3 - "linear-and-interactions": 10, # 1+a+b+c+ab+ac+bc+3 - "fully-quadratic": 13, # 1+a+b+c+a**2+b**2+c**2+ab+ac+bc+3 - } - - for formula, num_candidates in expected_num_candidates.items(): - data_model = data_models.DoEStrategy(domain=domain, formula=formula) - strategy = DoEStrategy(data_model=data_model) - candidates = strategy.ask() - assert candidates.shape == (num_candidates, 3) - - -def test_doe_strategy_correctness(): - candidates_fixed = pd.DataFrame( - np.array([[0.2, 0.2, 0.6], [0.3, 0.6, 0.1], [0.7, 0.1, 0.2], [0.3, 0.1, 0.6]]), - columns=["x1", "x2", "x3"], - ) - data_model = data_models.DoEStrategy(domain=domain, formula="linear") - strategy = DoEStrategy(data_model=data_model) - strategy.set_candidates(candidates_fixed) - candidates = strategy.ask(candidate_count=12) - - np.random.seed(1) - candidates_expected = np.array( - [[0.2, 0.2, 0.6], [0.3, 0.6, 0.1], [0.7, 0.1, 0.2], [0.3, 0.1, 0.6]] - ) - for row in candidates.to_numpy(): - assert any(np.allclose(row, o, atol=1e-2) for o in candidates_expected) - for o in candidates_expected[:-1]: - assert any(np.allclose(o, row, atol=1e-2) for row in candidates.to_numpy()) - - -def test_doe_strategy_amount_of_candidates(): - candidates_fixed = pd.DataFrame( - np.array([[0.2, 0.2, 0.6], [0.3, 0.6, 0.1], [0.7, 0.1, 0.2], [0.3, 0.1, 0.6]]), - columns=["x1", "x2", "x3"], - ) - data_model = data_models.DoEStrategy(domain=domain, formula="linear") - strategy = DoEStrategy(data_model=data_model) - strategy.set_candidates(candidates_fixed) - candidates = strategy.ask(candidate_count=12) - - np.random.seed(1) - num_candidates_expected = 12 - assert len(candidates) == num_candidates_expected - - -def test_categorical_discrete_doe(): - quantity_a = [ - ContinuousInput(key=f"quantity_a_{i}", bounds=(0, 100)) for i in range(3) - ] - quantity_b = [ - ContinuousInput(key=f"quantity_b_{i}", bounds=(0, 15)) for i in range(3) - ] - all_inputs = [ - CategoricalInput(key="animals", categories=["Whale", "Turtle", "Sloth"]), - DiscreteInput(key="discrete", values=[0.1, 0.2, 0.3, 1.6, 2]), - ContinuousInput(key="independent", bounds=(3, 10)), - ] - all_inputs.extend(quantity_a) - all_inputs.extend(quantity_b) - - all_constraints = [ - NChooseKConstraint( - features=[var.key for var in quantity_a], - min_count=0, - max_count=1, - none_also_valid=True, - ), - NChooseKConstraint( - features=[var.key for var in quantity_b], - min_count=0, - max_count=2, - none_also_valid=True, - ), - LinearEqualityConstraint( - features=[var.key for var in quantity_b], - coefficients=[1 for var in quantity_b], - rhs=15, - ), - ] - - n_experiments = 10 - domain = Domain( - inputs=all_inputs, - outputs=[ContinuousOutput(key="y")], - constraints=all_constraints, - ) - - data_model = data_models.DoEStrategy( - domain=domain, formula="linear", optimization_strategy="partially-random" - ) - strategy = DoEStrategy(data_model=data_model) - candidates = strategy.ask(candidate_count=n_experiments) - - assert candidates.shape == (10, 9) - - -def test_partially_fixed_experiments(): - continuous_var = [ - ContinuousInput(key=f"continuous_var_{i}", bounds=(100, 230)) for i in range(2) - ] - - all_constraints = [ - NChooseKConstraint( - features=[var.key for var in continuous_var], - min_count=1, - max_count=2, - none_also_valid=True, - ), - ] - all_inputs = [ - CategoricalInput(key="animal", categories=["dog", "whale", "cat"]), - CategoricalInput(key="plant", categories=["tulip", "sunflower"]), - DiscreteInput(key="a_discrete", values=[0.1, 0.2, 0.3, 1.6, 2]), - DiscreteInput(key="b_discrete", values=[0.1, 0.2, 0.3, 1.6, 2]), - ] - n_experiments = 10 - - all_inputs = all_inputs + continuous_var - domain = Domain( - inputs=all_inputs, - outputs=[ContinuousOutput(key="y")], - constraints=all_constraints, - ) - - data_model = data_models.DoEStrategy( - domain=domain, - formula="linear", - optimization_strategy="relaxed", - verbose=True, - ) - strategy = DoEStrategy(data_model=data_model) - strategy.set_candidates( - pd.DataFrame( - [ - [150, 100, 0.3, 0.2, None, None], - [0, 100, 0.3, 0.2, None, "tulip"], - [0, 100, None, 0.2, "dog", None], - [0, 100, 0.3, 0.2, "cat", "tulip"], - [None, 100, 0.3, None, None, None], - ], - columns=[ - "continuous_var_0", - "continuous_var_1", - "a_discrete", - "b_discrete", - "animal", - "plant", - ], - ) - ) - - only_partially_fixed = pd.DataFrame( - [ - [150, 100, 0.3, 0.2, None, None], - [0, 100, 0.3, 0.2, None, "tulip"], - [0, 100, None, 0.2, "dog", None], - [None, 100, 0.3, None, None, None], - ], - columns=[ - "continuous_var_0", - "continuous_var_1", - "a_discrete", - "b_discrete", - "animal", - "plant", - ], - ) - - candidates = strategy.ask(candidate_count=n_experiments) - print(candidates) - only_partially_fixed = only_partially_fixed.mask( - only_partially_fixed.isnull(), candidates[:4] - ) - test_df = pd.DataFrame(np.ones((4, 6))) - test_df = test_df.where(candidates[:4] == only_partially_fixed, 0) - assert test_df.sum().sum() == 0 - - -# if __name__ == "__main__": -# test_doe_strategy_ask() -# test_doe_strategy_ask_with_candidates() -# test_doe_categoricals_not_implemented() -# test_doe_discrete_not_implemented() -# test_nchoosek_implemented() -# test_formulas_implemented() -# test_doe_strategy_correctness() -# test_doe_strategy_amount_of_candidates() +import warnings + +import numpy as np +import pandas as pd + +import bofire.data_models.strategies.api as data_models +from bofire.data_models.constraints.api import ( + LinearEqualityConstraint, + LinearInequalityConstraint, + NChooseKConstraint, +) +from bofire.data_models.domain.api import Domain +from bofire.data_models.features.api import ( + CategoricalInput, + ContinuousInput, + ContinuousOutput, + DiscreteInput, +) +from bofire.strategies.api import DoEStrategy + +# from tests.bofire.strategies.botorch.test_model_spec import VALID_MODEL_SPEC_LIST + +warnings.filterwarnings("ignore", category=DeprecationWarning) +warnings.filterwarnings("ignore", category=UserWarning, append=True) + +with warnings.catch_warnings(): + warnings.simplefilter("ignore") + +inputs = [ + ContinuousInput( + key=f"x{1}", + bounds=(0.0, 1.0), + ), + ContinuousInput( + key=f"x{2}", + bounds=(0.1, 1.0), + ), + ContinuousInput( + key=f"x{3}", + bounds=(0.0, 0.6), + ), +] +domain = Domain.from_lists( + inputs=inputs, + outputs=[ContinuousOutput(key="y")], + constraints=[ + LinearEqualityConstraint( + features=[f"x{i + 1}" for i in range(3)], coefficients=[1, 1, 1], rhs=1 + ), + LinearInequalityConstraint(features=["x1", "x2"], coefficients=[5, 4], rhs=3.9), + LinearInequalityConstraint( + features=["x1", "x2"], coefficients=[-20, 5], rhs=-3 + ), + ], +) + + +def test_doe_strategy_init(): + data_model = data_models.DoEStrategy(domain=domain, formula="linear") + strategy = DoEStrategy(data_model=data_model) + assert strategy is not None + + +def test_doe_strategy_ask(): + data_model = data_models.DoEStrategy(domain=domain, formula="linear") + strategy = DoEStrategy(data_model=data_model) + candidates = strategy.ask(candidate_count=12) + assert candidates.shape == (12, 3) + + +def test_doe_strategy_ask_with_candidates(): + candidates_fixed = pd.DataFrame( + np.array([[0.2, 0.2, 0.6], [0.3, 0.6, 0.1], [0.7, 0.1, 0.2], [0.3, 0.1, 0.6]]), + columns=["x1", "x2", "x3"], + ) + data_model = data_models.DoEStrategy(domain=domain, formula="linear") + strategy = DoEStrategy(data_model=data_model) + strategy.set_candidates(candidates_fixed) + candidates = strategy.ask(candidate_count=12) + assert candidates.shape == (12, 3) + + +def test_nchoosek_implemented(): + nchoosek_constraint = NChooseKConstraint( + features=[f"x{i + 1}" for i in range(3)], + min_count=0, + max_count=2, + none_also_valid=True, + ) + domain = Domain.from_lists( + inputs=[ContinuousInput(key=f"x{i + 1}", bounds=(0.0, 1.0)) for i in range(3)], + outputs=[ContinuousOutput(key="y")], + constraints=[nchoosek_constraint], + ) + data_model = data_models.DoEStrategy( + domain=domain, formula="linear", optimization_strategy="partially-random" + ) + strategy = DoEStrategy(data_model=data_model) + candidates = strategy.ask(candidate_count=12) + assert candidates.shape == (12, 3) + + +def test_formulas_implemented(): + expected_num_candidates = { + "linear": 7, # 1+a+b+c+3 + "linear-and-quadratic": 10, # 1+a+b+c+a**2+b**2+c**2+3 + "linear-and-interactions": 10, # 1+a+b+c+ab+ac+bc+3 + "fully-quadratic": 13, # 1+a+b+c+a**2+b**2+c**2+ab+ac+bc+3 + } + + for formula, num_candidates in expected_num_candidates.items(): + data_model = data_models.DoEStrategy(domain=domain, formula=formula) + strategy = DoEStrategy(data_model=data_model) + candidates = strategy.ask() + assert candidates.shape == (num_candidates, 3) + + +def test_doe_strategy_correctness(): + candidates_fixed = pd.DataFrame( + np.array([[0.2, 0.2, 0.6], [0.3, 0.6, 0.1], [0.7, 0.1, 0.2], [0.3, 0.1, 0.6]]), + columns=["x1", "x2", "x3"], + ) + data_model = data_models.DoEStrategy(domain=domain, formula="linear") + strategy = DoEStrategy(data_model=data_model) + strategy.set_candidates(candidates_fixed) + candidates = strategy.ask(candidate_count=12) + + np.random.seed(1) + candidates_expected = np.array( + [[0.2, 0.2, 0.6], [0.3, 0.6, 0.1], [0.7, 0.1, 0.2], [0.3, 0.1, 0.6]] + ) + for row in candidates.to_numpy(): + assert any(np.allclose(row, o, atol=1e-2) for o in candidates_expected) + for o in candidates_expected[:-1]: + assert any(np.allclose(o, row, atol=1e-2) for row in candidates.to_numpy()) + + +def test_doe_strategy_amount_of_candidates(): + candidates_fixed = pd.DataFrame( + np.array([[0.2, 0.2, 0.6], [0.3, 0.6, 0.1], [0.7, 0.1, 0.2], [0.3, 0.1, 0.6]]), + columns=["x1", "x2", "x3"], + ) + data_model = data_models.DoEStrategy(domain=domain, formula="linear") + strategy = DoEStrategy(data_model=data_model) + strategy.set_candidates(candidates_fixed) + candidates = strategy.ask(candidate_count=12) + + np.random.seed(1) + num_candidates_expected = 12 + assert len(candidates) == num_candidates_expected + + +def test_categorical_discrete_doe(): + quantity_a = [ + ContinuousInput(key=f"quantity_a_{i}", bounds=(0, 100)) for i in range(3) + ] + quantity_b = [ + ContinuousInput(key=f"quantity_b_{i}", bounds=(0, 15)) for i in range(3) + ] + all_inputs = [ + CategoricalInput(key="animals", categories=["Whale", "Turtle", "Sloth"]), + DiscreteInput(key="discrete", values=[0.1, 0.2, 0.3, 1.6, 2]), + ContinuousInput(key="independent", bounds=(3, 10)), + ] + all_inputs.extend(quantity_a) + all_inputs.extend(quantity_b) + + all_constraints = [ + NChooseKConstraint( + features=[var.key for var in quantity_a], + min_count=0, + max_count=1, + none_also_valid=True, + ), + NChooseKConstraint( + features=[var.key for var in quantity_b], + min_count=0, + max_count=2, + none_also_valid=True, + ), + LinearEqualityConstraint( + features=[var.key for var in quantity_b], + coefficients=[1 for var in quantity_b], + rhs=15, + ), + ] + + n_experiments = 10 + domain = Domain( + inputs=all_inputs, + outputs=[ContinuousOutput(key="y")], + constraints=all_constraints, + ) + + data_model = data_models.DoEStrategy( + domain=domain, formula="linear", optimization_strategy="partially-random" + ) + strategy = DoEStrategy(data_model=data_model) + candidates = strategy.ask(candidate_count=n_experiments) + + assert candidates.shape == (10, 9) + + +def test_partially_fixed_experiments(): + continuous_var = [ + ContinuousInput(key=f"continuous_var_{i}", bounds=(100, 230)) for i in range(2) + ] + + all_constraints = [ + NChooseKConstraint( + features=[var.key for var in continuous_var], + min_count=1, + max_count=2, + none_also_valid=True, + ), + ] + all_inputs = [ + CategoricalInput(key="animal", categories=["dog", "whale", "cat"]), + CategoricalInput(key="plant", categories=["tulip", "sunflower"]), + DiscreteInput(key="a_discrete", values=[0.1, 0.2, 0.3, 1.6, 2]), + DiscreteInput(key="b_discrete", values=[0.1, 0.2, 0.3, 1.6, 2]), + ] + n_experiments = 10 + + all_inputs = all_inputs + continuous_var + domain = Domain( + inputs=all_inputs, + outputs=[ContinuousOutput(key="y")], + constraints=all_constraints, + ) + + data_model = data_models.DoEStrategy( + domain=domain, + formula="linear", + optimization_strategy="relaxed", + verbose=True, + ) + strategy = DoEStrategy(data_model=data_model) + strategy.set_candidates( + pd.DataFrame( + [ + [150, 100, 0.3, 0.2, None, None], + [0, 100, 0.3, 0.2, None, "tulip"], + [0, 100, None, 0.2, "dog", None], + [0, 100, 0.3, 0.2, "cat", "tulip"], + [None, 100, 0.3, None, None, None], + ], + columns=[ + "continuous_var_0", + "continuous_var_1", + "a_discrete", + "b_discrete", + "animal", + "plant", + ], + ) + ) + + only_partially_fixed = pd.DataFrame( + [ + [150, 100, 0.3, 0.2, None, None], + [0, 100, 0.3, 0.2, None, "tulip"], + [0, 100, None, 0.2, "dog", None], + [None, 100, 0.3, None, None, None], + ], + columns=[ + "continuous_var_0", + "continuous_var_1", + "a_discrete", + "b_discrete", + "animal", + "plant", + ], + ) + + candidates = strategy.ask(candidate_count=n_experiments) + print(candidates) + only_partially_fixed = only_partially_fixed.mask( + only_partially_fixed.isnull(), candidates[:4] + ) + test_df = pd.DataFrame(np.ones((4, 6))) + test_df = test_df.where(candidates[:4] == only_partially_fixed, 0) + assert test_df.sum().sum() == 0 + + +def test_categorical_doe_iterative(): + quantity_a = [ + ContinuousInput(key=f"quantity_a_{i}", bounds=(20, 100)) for i in range(2) + ] + all_inputs = [ + ContinuousInput(key="independent", bounds=(3, 10)), + ] + all_inputs.extend(quantity_a) + + all_constraints = [ + NChooseKConstraint( + features=[var.key for var in quantity_a], + min_count=1, + max_count=1, + none_also_valid=False, + ), + ] + + n_experiments = 5 + domain = Domain( + inputs=all_inputs, + outputs=[ContinuousOutput(key="y")], + constraints=all_constraints, + ) + + data_model = data_models.DoEStrategy( + domain=domain, + formula="linear", + optimization_strategy="iterative", + ) + strategy = DoEStrategy(data_model=data_model) + candidates = strategy.ask( + candidate_count=n_experiments, raise_validation_error=False + ) + + assert candidates.shape == (5, 3) + + +if __name__ == "__main__": + test_categorical_doe_iterative() From 4be71bf57ec6d7bab2c32fa8fa259dfe3b16050b Mon Sep 17 00:00:00 2001 From: linznedd Date: Fri, 20 Oct 2023 11:34:15 +0200 Subject: [PATCH 26/29] added iterative strategy --- bofire/data_models/strategies/doe.py | 7 ++++- bofire/strategies/doe_strategy.py | 39 ++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/bofire/data_models/strategies/doe.py b/bofire/data_models/strategies/doe.py index 031007e3e..94bae4b0c 100644 --- a/bofire/data_models/strategies/doe.py +++ b/bofire/data_models/strategies/doe.py @@ -21,7 +21,12 @@ class DoEStrategy(Strategy): str, ] optimization_strategy: Literal[ - "default", "exhaustive", "branch-and-bound", "partially-random", "relaxed" + "default", + "exhaustive", + "branch-and-bound", + "partially-random", + "relaxed", + "iterative", ] = "default" verbose: bool = False diff --git a/bofire/strategies/doe_strategy.py b/bofire/strategies/doe_strategy.py index ff75f67fd..8f92b0e2b 100644 --- a/bofire/strategies/doe_strategy.py +++ b/bofire/strategies/doe_strategy.py @@ -133,6 +133,45 @@ def _ask(self, candidate_count: PositiveInt) -> pd.DataFrame: categorical_groups=all_new_categories, discrete_variables=new_discretes, ) + elif self.data_model.optimization_strategy in [ + "iterative", + ]: + # a dynamic programming approach to shrink the optimization space by optimizing one experiment at a time + assert ( + _candidate_count is not None + ), "strategy iterative requires number of experiments to be set!" + _adapted_partially_fixed_candidates = adapted_partially_fixed_candidates + + num_adapted_partially_fixed_candidates = 0 + if adapted_partially_fixed_candidates is not None: + num_adapted_partially_fixed_candidates = len( + adapted_partially_fixed_candidates + ) + _design = None + for i in range(_candidate_count): + _design = find_local_max_ipopt_BaB( + domain=new_domain, + model_type=self.formula, + n_experiments=num_adapted_partially_fixed_candidates + i + 1, + fixed_experiments=None, + verbose=self.data_model.verbose, + partially_fixed_experiments=_adapted_partially_fixed_candidates, + categorical_groups=all_new_categories, + discrete_variables=new_discretes, + ) + _adapted_partially_fixed_candidates = pd.concat( + [ + _adapted_partially_fixed_candidates, + _design.round(6).tail(1), + ], + axis=0, + ignore_index=True, + ) + print( + f"Status: {i+1} of {_candidate_count} experiments determined \n" + f"Current experimental plan:\n {design_from_new_to_original_domain(self.domain, _design)}" + ) + design = _design else: raise RuntimeError("Could not find suitable optimization strategy") From e065647a3c34daf3db923e183d5fe69eb9fc91a8 Mon Sep 17 00:00:00 2001 From: Dominik Linzner <44400328+dlinzner-bcs@users.noreply.github.com> Date: Fri, 20 Oct 2023 12:16:53 +0200 Subject: [PATCH 27/29] 286 add surrogate models that can extrapolate (#300) (#303) * add polynomial kernel * add polynomial kernel * add polynomial kernel * add polynomial kernel * add quadratic surrogate * adding linear kernel not necessary * adding linear kernel not necessary * adding linear kernel not necessary * fix test * change kernel tzpe hint * generalize quadratic surrogate * generalize quadratic surrogate * generalize quadratic surrogate * fix init * fix init * fix init * fix init * fix init * add polynomial_surrogate to AnyBotochSurrogate * add test that botorch surrogate can be composed * add test that botorch surrogate can be composed * add test that botorch surrogate can be composed * add test that botorch surrogate can be composed * add test that botorch surrogate can be composed From fd638b1d7169f8fc21910ba003a4fe026fda13f1 Mon Sep 17 00:00:00 2001 From: linznedd Date: Fri, 20 Oct 2023 15:15:17 +0200 Subject: [PATCH 28/29] rmv unneccessary private flagged variables --- bofire/strategies/doe_strategy.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/bofire/strategies/doe_strategy.py b/bofire/strategies/doe_strategy.py index 8f92b0e2b..dfb0a9633 100644 --- a/bofire/strategies/doe_strategy.py +++ b/bofire/strategies/doe_strategy.py @@ -140,38 +140,37 @@ def _ask(self, candidate_count: PositiveInt) -> pd.DataFrame: assert ( _candidate_count is not None ), "strategy iterative requires number of experiments to be set!" - _adapted_partially_fixed_candidates = adapted_partially_fixed_candidates num_adapted_partially_fixed_candidates = 0 if adapted_partially_fixed_candidates is not None: num_adapted_partially_fixed_candidates = len( adapted_partially_fixed_candidates ) - _design = None + design = None for i in range(_candidate_count): - _design = find_local_max_ipopt_BaB( + design = find_local_max_ipopt_BaB( domain=new_domain, model_type=self.formula, n_experiments=num_adapted_partially_fixed_candidates + i + 1, fixed_experiments=None, verbose=self.data_model.verbose, - partially_fixed_experiments=_adapted_partially_fixed_candidates, + partially_fixed_experiments=adapted_partially_fixed_candidates, categorical_groups=all_new_categories, discrete_variables=new_discretes, ) - _adapted_partially_fixed_candidates = pd.concat( + adapted_partially_fixed_candidates = pd.concat( [ - _adapted_partially_fixed_candidates, - _design.round(6).tail(1), + adapted_partially_fixed_candidates, + design.round(6).tail(1), ], axis=0, ignore_index=True, ) print( f"Status: {i+1} of {_candidate_count} experiments determined \n" - f"Current experimental plan:\n {design_from_new_to_original_domain(self.domain, _design)}" + f"Current experimental plan:\n {design_from_new_to_original_domain(self.domain, design)}" ) - design = _design + else: raise RuntimeError("Could not find suitable optimization strategy") From d87aa1e724d4cdb38ab4a35fd946f7ad60011670 Mon Sep 17 00:00:00 2001 From: linznedd Date: Mon, 23 Oct 2023 09:25:45 +0200 Subject: [PATCH 29/29] made strategy selection clearer --- bofire/strategies/doe_strategy.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bofire/strategies/doe_strategy.py b/bofire/strategies/doe_strategy.py index dfb0a9633..f0532ade8 100644 --- a/bofire/strategies/doe_strategy.py +++ b/bofire/strategies/doe_strategy.py @@ -133,9 +133,7 @@ def _ask(self, candidate_count: PositiveInt) -> pd.DataFrame: categorical_groups=all_new_categories, discrete_variables=new_discretes, ) - elif self.data_model.optimization_strategy in [ - "iterative", - ]: + elif self.data_model.optimization_strategy == "iterative": # a dynamic programming approach to shrink the optimization space by optimizing one experiment at a time assert ( _candidate_count is not None