Example for using a synthetic BoTorch test function in a continuous searchspace

Example for using the synthetic test functions in a continuous spaces. All test functions that are available in BoTorch are also available here and wrapped via the botorch_function_wrapper.

This example assumes some basic familiarity with using BayBE. We thus refer to campaign for a basic example. Also, there is a large overlap with other examples with regards to using the test function. We thus refer to discrete_space for details on this aspect.

Necessary imports for this example

from botorch.test_functions import Rastrigin
from baybe import Campaign
from baybe.objective import Objective
from baybe.parameters import NumericalContinuousParameter
from baybe.searchspace import SearchSpace
from baybe.targets import NumericalTarget
from baybe.utils.botorch_wrapper import botorch_function_wrapper

Defining the test function

See discrete_space for details.

DIMENSION = 4
TestFunctionClass = Rastrigin
if not hasattr(TestFunctionClass, "dim"):
    TestFunction = TestFunctionClass(dim=DIMENSION)
elif TestFunctionClass().dim == DIMENSION:
    TestFunction = TestFunctionClass()
else:
    print(
        f"\nYou choose a dimension of {DIMENSION} for the test function"
        f"{TestFunctionClass}. However, this function can only be used in "
        f"{TestFunctionClass().dim} dimension, so the provided dimension is replaced. "
        "Also, DISC_INDICES and CONT_INDICES will be re-written."
    )
    TestFunction = TestFunctionClass()
    DIMENSION = TestFunctionClass().dim
    DISC_INDICES = list(range(0, (DIMENSION + 1) // 2))
    CONT_INDICES = list(range((DIMENSION + 1) // 2, DIMENSION))
BOUNDS = TestFunction.bounds
WRAPPED_FUNCTION = botorch_function_wrapper(test_function=TestFunction)

Creating the searchspace and the objective

Since the searchspace is continuous test, we construct NumericalContinuousParameters We use that data of the test function to deduce bounds and number of parameters.

parameters = [
    NumericalContinuousParameter(
        name=f"x_{k+1}",
        bounds=(BOUNDS[0, k], BOUNDS[1, k]),
    )
    for k in range(DIMENSION)
]
searchspace = SearchSpace.from_product(parameters=parameters)
objective = Objective(
    mode="SINGLE", targets=[NumericalTarget(name="Target", mode="MIN")]
)

Constructing the campaign and performing a recommendation

campaign = Campaign(
    searchspace=searchspace,
    objective=objective,
)

Get a recommendation for a fixed batch size.

BATCH_SIZE = 3
recommendation = campaign.recommend(batch_size=BATCH_SIZE)

Evaluate the test function. Note that we need iterate through the rows of the recommendation. Furthermore, we need to interpret the row as a list.

target_values = []
for index, row in recommendation.iterrows():
    target_values.append(WRAPPED_FUNCTION(*row.to_list()))

We add an additional column with the calculated target values.

recommendation["Target"] = target_values

Here, we inform the campaign about our measurement.

campaign.add_measurements(recommendation)
print("\n\nRecommended experiments with measured values: ")
print(recommendation)
Recommended experiments with measured values: 
        x_1       x_2       x_3       x_4     Target
0 -4.055524 -1.283530 -0.788693  4.308326  71.147957
1 -1.658197  0.757722 -1.042633 -4.908261  55.442822
2 -1.053124 -1.860778  1.158596 -3.043124  24.246902