# Example for using synthetic test functions in hybrid spaces This examples shows how to optimize a custom test function in a hybrid searchspace. It focuses on the searchspace-related aspects and not on the custom test function. This example assumes some basic familiarity with using BayBE and synthetic test functions. We thus refer to [`campaign`](./../Basics/campaign.md) for a basic example. For details on using synthetic test functions, we refer to other examples in this directory. ## Necessary imports for this example ```python import numpy as np from botorch.test_functions import Rastrigin ``` ```python from baybe import Campaign from baybe.objectives import SingleTargetObjective from baybe.parameters import NumericalContinuousParameter, NumericalDiscreteParameter from baybe.recommenders import NaiveHybridSpaceRecommender, TwoPhaseMetaRecommender from baybe.searchspace import SearchSpace from baybe.targets import NumericalTarget from baybe.utils.botorch_wrapper import botorch_function_wrapper ``` ## Defining the test function and the hybrid dimensions See [`discrete_space`](./discrete_space.md) for details on the test function. ```python DIMENSION = 6 ``` This examples uses two lists containing the discrete resp. discrete dimensions. Together, these should contain the integers `0,1,...,DIMENSION-1`. Although this is checked also in this file, you should configure these indices manually manually here and verify that the experiment you set up is configured correctly. In particular, if the function that you want to use is only available for a fixed dimension, then these will be overwritten by distributing the first half of the dimension to `DISC_INDICES` and the remaining ones to `CONT_INDICES`. ```python DISC_INDICES = [0, 1, 2] CONT_INDICES = [3, 4, 5] ``` ```python TestFunctionClass = Rastrigin ``` This part checks if the test function already has a fixed dimension. In that case, we print a warning and replace DIMENSION. ```python 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)) ``` This check verifies if the union of the given index sets yields indices matching `DIMENSION`. If this fails, then either the intersection between the index sets is not empty or the test function has another dimension. Note that this might in particular happen for test functions that ignore the `dim` keyword! ```python if set(CONT_INDICES + DISC_INDICES) != set(range(DIMENSION)): raise ValueError( "Either the intersection between CONT_IND and DISC_IND is not empty or your " "indices do not match." ) ``` ```python BOUNDS = TestFunction.bounds WRAPPED_FUNCTION = botorch_function_wrapper(test_function=TestFunction) ``` ## Constructing the hybrid searchspace The following parameter decides how many points each discrete dimension should have. ```python POINTS_PER_DIM = 3 ``` Construct the continuous parameters as `NumericContinuous` parameters. ```python cont_parameters = [ NumericalContinuousParameter( name=f"x_{k+1}", bounds=(BOUNDS[0, k], BOUNDS[1, k]), ) for k in CONT_INDICES ] ``` Construct the discrete parameters as `NumericalDiscreteParameters`. ```python disc_parameters = [ NumericalDiscreteParameter( name=f"x_{k+1}", values=list(np.linspace(BOUNDS[0, k], BOUNDS[1, k], POINTS_PER_DIM)), tolerance=0.01, ) for k in DISC_INDICES ] ``` ```python searchspace = SearchSpace.from_product(parameters=disc_parameters + cont_parameters) objective = SingleTargetObjective(target=NumericalTarget(name="Target", mode="MIN")) ``` ## Constructing hybrid recommenders Here, we explicitly create a recommender object to use the `NaiveHybridSpaceRecommender`. The keywords `disc_recommender` and `cont_recommender` can be used to select different recommenders for the corresponding subspaces. We use the default choices, which is the `BotorchRecommender`. ```python hybrid_recommender = TwoPhaseMetaRecommender(recommender=NaiveHybridSpaceRecommender()) ``` ## Constructing the campaign and performing a recommendation ```python campaign = Campaign( searchspace=searchspace, objective=objective, recommender=hybrid_recommender, ) ``` ```python # 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. ```python 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. ```python recommendation["Target"] = target_values ``` Here, we inform the campaign about our measurement. ```python 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 x_5 x_6 Target 4 -5.12 0.00 0.00 -5.062950 -3.598000 -3.560977 118.394821 15 0.00 5.12 -5.12 2.518722 4.923163 3.822993 129.692825 22 5.12 0.00 0.00 1.017797 -1.788585 -2.351331 62.295483