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
for a basic example.
Necessary imports for this example¶
import os
import numpy as np
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¶
SMOKE_TEST = "SMOKE_TEST" in os.environ
FRAC_RESOLUTION = 3 if SMOKE_TEST else 7
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"])
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.
constraint = DiscreteDependenciesConstraint(
parameters=["Switch1", "Switch2"],
conditions=[
SubSelectionCondition(selection=["on"]),
SubSelectionCondition(selection=["right"]),
],
affected_parameters=[["Solv", "Frac1"], ["FrameA", "FrameB"]],
)
Creating the searchspace and the objective¶
searchspace = SearchSpace.from_product(parameters=parameters, constraints=[constraint])
objective = SingleTargetObjective(target=NumericalTarget(name="Target_1", mode="MAX"))
Creating and printing the campaign¶
campaign = Campaign(searchspace=searchspace, objective=objective)
print(campaign)
Campaign
Meta Data
Batches done: 0
Fits done: 0
SearchSpace
Search Space Type: DISCRETE
SubspaceDiscrete
Discrete Parameters
Name Type Num_Values Encoding
0 Frac1 NumericalDis... 3 None
1 FrameA CategoricalP... 2 CategoricalE...
2 FrameB CategoricalP... 2 CategoricalE...
3 Solv SubstancePar... 2 SubstanceEnc...
4 Switch1 CategoricalP... 2 CategoricalE...
5 Switch2 CategoricalP... 2 CategoricalE...
Experimental Representation
Solv Switch1 ... FrameA FrameB
0 C1 off ... A A
1 C1 on ... A A
2 water on ... A A
.. ... ... ... ... ...
32 water on ... A B
33 water on ... B A
34 water on ... B B
[35 rows x 6 columns]
Meta Data
was_recommended: 0/35
was_measured: 0/35
dont_recommend: 0/35
Constraints
Type Affected_Paramet
0 DiscreteDepe... [Switch1, Sw...
Computational Representation
Frac1 FrameA_A ... Switch2_left Switch2_right
0 0.0 1.0 ... 1.0 0.0
1 0.0 1.0 ... 1.0 0.0
2 0.0 1.0 ... 1.0 0.0
.. ... ... ... ... ...
32 100.0 1.0 ... 0.0 1.0
33 100.0 0.0 ... 0.0 1.0
34 100.0 0.0 ... 0.0 1.0
[35 rows x 10 columns]
Objective
Type: SingleTargetObjective
Targets
Type Name ... Upper_Bound Transformation
0 NumericalTarget Target_1 ... inf None
[1 rows x 6 columns]
TwoPhaseMetaRecommender
Initial recommender
RandomRecommender
Compatibility: SearchSpaceType.HYBRID
Recommender
BotorchRecommender
Surrogate
GaussianProcessSurrogate
Supports Transfer Learning: True
Kernel factory: DefaultKernelFactory()
Acquisition function: qLogExpectedImprovement()
Compatibility: SearchSpaceType.HYBRID
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.
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