# Example for using dependency constraints in discrete searchspaces This example shows how a dependency constraint can be created for a discrete searchspace. For instance, some parameters might only be relevant when another parameter has a certain value. All dependencies have to be declared in a single constraint. This example assumes some basic familiarity with using BayBE. We thus refer to [`campaign`](./../Basics/campaign.md) for a basic example. ## Necessary imports for this example ```python import os ``` ```python import numpy as np ``` ```python from baybe import Campaign from baybe.constraints import DiscreteDependenciesConstraint, SubSelectionCondition from baybe.objectives import SingleTargetObjective from baybe.parameters import ( CategoricalParameter, NumericalDiscreteParameter, SubstanceParameter, ) from baybe.searchspace import SearchSpace from baybe.targets import NumericalTarget from baybe.utils.dataframe import add_fake_results ``` ## Experiment setup ```python SMOKE_TEST = "SMOKE_TEST" in os.environ FRAC_RESOLUTION = 3 if SMOKE_TEST else 7 ``` ```python dict_solvent = { "water": "O", "C1": "C", } solvent = SubstanceParameter(name="Solv", data=dict_solvent, encoding="MORDRED") switch1 = CategoricalParameter(name="Switch1", values=["on", "off"]) switch2 = CategoricalParameter(name="Switch2", values=["left", "right"]) fraction1 = NumericalDiscreteParameter( name="Frac1", values=list(np.linspace(0, 100, FRAC_RESOLUTION)), tolerance=0.2 ) frame1 = CategoricalParameter(name="FrameA", values=["A", "B"]) frame2 = CategoricalParameter(name="FrameB", values=["A", "B"]) ``` ```python parameters = [solvent, switch1, switch2, fraction1, frame1, frame2] ``` ## Creating the constraints The constraints are handled when creating the searchspace object. It is thus necessary to define it before the searchspace creation. Note that multiple dependencies have to be included in a single constraint object. ```python constraint = DiscreteDependenciesConstraint( parameters=["Switch1", "Switch2"], conditions=[ SubSelectionCondition(selection=["on"]), SubSelectionCondition(selection=["right"]), ], affected_parameters=[["Solv", "Frac1"], ["FrameA", "FrameB"]], ) ``` ## Creating the searchspace and the objective ```python searchspace = SearchSpace.from_product(parameters=parameters, constraints=[constraint]) ``` ```python objective = SingleTargetObjective(target=NumericalTarget(name="Target_1", mode="MAX")) ``` ## Creating and printing the campaign ```python campaign = Campaign(searchspace=searchspace, objective=objective) print(campaign) ``` Campaign Meta Data Batches Done: 0 Fits Done: 0 Search Space Search Space Type: DISCRETE Discrete Search Space Discrete Parameters Name Type Num_Values Encoding 0 Solv SubstanceParameter 2 SubstanceEncoding.MORDRED 1 Switch1 CategoricalParameter 2 CategoricalEncoding.OHE 2 Switch2 CategoricalParameter 2 CategoricalEncoding.OHE 3 Frac1 NumericalDiscreteParameter 3 None 4 FrameA CategoricalParameter 2 CategoricalEncoding.OHE 5 FrameB CategoricalParameter 2 CategoricalEncoding.OHE Experimental Representation Solv Switch1 ... FrameA FrameB 0 water on ... A A 1 water on ... A A 2 water on ... A A .. ... ... ... ... ... 32 C1 on ... A B 33 C1 on ... B A 34 C1 on ... B B [35 rows x 6 columns] Metadata: was_recommended: 0/35 was_measured: 0/35 dont_recommend: 0/35 Constraints Type Affected_Parameters 0 DiscreteDependenciesConstraint [Switch1, Switch2] Computational Representation Solv_MORDRED_nAtom Switch1_on ... FrameB_A FrameB_B 0 3.0 1.0 ... 1.0 0.0 1 3.0 1.0 ... 1.0 0.0 2 3.0 1.0 ... 1.0 0.0 .. ... ... ... ... ... 32 5.0 1.0 ... 0.0 1.0 33 5.0 1.0 ... 1.0 0.0 34 5.0 1.0 ... 0.0 1.0 [35 rows x 10 columns] Objective Type: SingleTargetObjective Targets  Type Name Mode Lower_Bound Upper_Bound Transformation 0 NumericalTarget Target_1 MAX -inf inf None TwoPhaseMetaRecommender(initial_recommender=RandomRecommender(allow_repeated_recomm endations=False, allow_recommending_already_measured=True), recommender=BotorchRecommender(allow_repeated_recommendations=False, allow_recommending_already_measured=True, surrogate_model=GaussianProcessSurrogate(kernel_factory=DefaultKernelFactory(), _model=None), acquisition_function=qLogExpectedImprovement(), _botorch_acqf=None, acquisition_function_cls=None, sequential_continuous=False, hybrid_sampler=None, sampling_percentage=1.0), switch_after=1) ## Manual verification of the constraints The following loop performs some recommendations and manually verifies the given constraints. ```python N_ITERATIONS = 2 if SMOKE_TEST else 5 for kIter in range(N_ITERATIONS): print(f"\n#### ITERATION {kIter+1} ####") print("## ASSERTS ##") print( f"Number entries with both switches on " f"(expected {7*len(dict_solvent)*2*2}): ", ( (campaign.searchspace.discrete.exp_rep["Switch1"] == "on") & (campaign.searchspace.discrete.exp_rep["Switch2"] == "right") ).sum(), ) print( f"Number entries with Switch1 off " f"(expected {2*2}): ", ( (campaign.searchspace.discrete.exp_rep["Switch1"] == "off") & (campaign.searchspace.discrete.exp_rep["Switch2"] == "right") ).sum(), ) print( f"Number entries with Switch2 off " f"(expected {7*len(dict_solvent)}):" f" ", ( (campaign.searchspace.discrete.exp_rep["Switch1"] == "on") & (campaign.searchspace.discrete.exp_rep["Switch2"] == "left") ).sum(), ) print( "Number entries with both switches off (expected 1): ", ( (campaign.searchspace.discrete.exp_rep["Switch1"] == "off") & (campaign.searchspace.discrete.exp_rep["Switch2"] == "left") ).sum(), ) rec = campaign.recommend(batch_size=5) add_fake_results(rec, campaign.targets) campaign.add_measurements(rec) ``` #### ITERATION 1 #### ## ASSERTS ## Number entries with both switches on (expected 56): 24 Number entries with Switch1 off (expected 4): 4 Number entries with Switch2 off (expected 14): 6 Number entries with both switches off (expected 1): 1 #### ITERATION 2 #### ## ASSERTS ## Number entries with both switches on (expected 56): 24 Number entries with Switch1 off (expected 4): 4 Number entries with Switch2 off (expected 14): 6 Number entries with both switches off (expected 1): 1