Example for using exclusion constraints in discrete searchspaces

This examples shows how an exclusion constraint can be created for a discrete searchspace. This can be used if some parameter values are incompatible with values of another parameter.

This example assumes some basic familiarity with using BayBE. We thus refer to campaign for a basic example.

Necessary imports for this example

import numpy as np
from baybe import Campaign
from baybe.constraints import (
    DiscreteExcludeConstraint,
    SubSelectionCondition,
    ThresholdCondition,
)
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

We begin by setting up some parameters for our experiments.

dict_solvent = {
    "water": "O",
    "C1": "C",
    "C2": "CC",
    "C3": "CCC",
    "C4": "CCCC",
    "C5": "CCCCC",
    "c6": "c1ccccc1",
    "C6": "CCCCCC",
}
solvent = SubstanceParameter(name="Solv", data=dict_solvent, encoding="RDKIT")
speed = CategoricalParameter(
    name="Speed",
    values=["very slow", "slow", "normal", "fast", "very fast"],
    encoding="INT",
)
temperature = NumericalDiscreteParameter(
    name="Temp", values=list(np.linspace(100, 200, 15)), tolerance=0.4
)
pressure = NumericalDiscreteParameter(
    name="Pressure", values=[1, 2, 5, 10], tolerance=0.4
)
parameters = [solvent, speed, temperature, pressure]

Creating the constraint

This constraint simulates a situation where solvents C2 and C4 are not compatible with temperatures larger than 151 and should thus be excluded.

constraint_1 = DiscreteExcludeConstraint(
    parameters=["Temp", "Solv"],
    combiner="AND",
    conditions=[
        ThresholdCondition(threshold=151, operator=">"),
        SubSelectionCondition(selection=["C4", "C2"]),
    ],
)

This constraint simulates a situation where solvents C5 and C6 are not compatible with pressures larger than 5 and should thus be excluded.

constraint_2 = DiscreteExcludeConstraint(
    parameters=["Pressure", "Solv"],
    combiner="AND",
    conditions=[
        ThresholdCondition(threshold=5, operator=">"),
        SubSelectionCondition(selection=["C5", "C6"]),
    ],
)

This constraint simulates a situation where pressures below 3 should never be combined with temperatures above 120.

constraint_3 = DiscreteExcludeConstraint(
    parameters=["Pressure", "Temp"],
    combiner="AND",
    conditions=[
        ThresholdCondition(threshold=3.0, operator="<"),
        ThresholdCondition(threshold=120.0, operator=">"),
    ],
)

Creating the searchspace and the objective

We now create the searchspace using the previously defined constraints.

searchspace = SearchSpace.from_product(
    parameters=parameters, constraints=[constraint_1, constraint_2, constraint_3]
)
[17:01:06] DEPRECATION WARNING: please use MorganGenerator
[17:01:06] DEPRECATION WARNING: please use MorganGenerator
[17:01:06] DEPRECATION WARNING: please use MorganGenerator
[17:01:06] DEPRECATION WARNING: please use MorganGenerator
[17:01:06] DEPRECATION WARNING: please use MorganGenerator
[17:01:06] DEPRECATION WARNING: please use MorganGenerator
[17:01:06] DEPRECATION WARNING: please use MorganGenerator
[17:01:06] DEPRECATION WARNING: please use MorganGenerator
[17:01:06] DEPRECATION WARNING: please use MorganGenerator
[17:01:06] DEPRECATION WARNING: please use MorganGenerator
[17:01:06] DEPRECATION WARNING: please use MorganGenerator
[17:01:06] DEPRECATION WARNING: please use MorganGenerator
[17:01:06] DEPRECATION WARNING: please use MorganGenerator
[17:01:06] DEPRECATION WARNING: please use MorganGenerator
[17:01:06] DEPRECATION WARNING: please use MorganGenerator
[17:01:06] DEPRECATION WARNING: please use MorganGenerator
[17:01:06] DEPRECATION WARNING: please use MorganGenerator
[17:01:06] DEPRECATION WARNING: please use MorganGenerator
[17:01:06] DEPRECATION WARNING: please use MorganGenerator
[17:01:06] DEPRECATION WARNING: please use MorganGenerator
[17:01:06] DEPRECATION WARNING: please use MorganGenerator
[17:01:06] DEPRECATION WARNING: please use MorganGenerator
[17:01:06] DEPRECATION WARNING: please use MorganGenerator
[17:01:06] DEPRECATION WARNING: please use MorganGenerator
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  Pressure  NumericalDis...           4             None
            1      Solv  SubstancePar...           8  SubstanceEnc...
            2     Speed  CategoricalP...           5  CategoricalE...
            3      Temp  NumericalDis...          15             None
         Experimental Representation
                   Solv      Speed     Temp  Pressure
            0        C1       fast  100.000       1.0
            1        C1       fast  100.000       2.0
            2        C1       fast  100.000       5.0
            ...     ...        ...      ...       ...
            1147  water  very slow  192.857      10.0
            1148  water  very slow  200.000       5.0
            1149  water  very slow  200.000      10.0
            
            [1150 rows x 4 columns]
         Meta Data
            was_recommended: 0/1150
            was_measured: 0/1150
            dont_recommend: 0/1150
         Constraints
                          Type Affected_Paramet
            0  DiscreteExcl...     [Temp, Solv]
            1  DiscreteExcl...  [Pressure, S...
            2  DiscreteExcl...  [Pressure, T...
         Computational Representation
                  Pressure  Solv_RDKIT_MaxAb  ...  Speed     Temp
            0          1.0              0.0   ...    0.0  100.000
            1          2.0              0.0   ...    0.0  100.000
            2          5.0              0.0   ...    0.0  100.000
            ...        ...              ...   ...    ...      ...
            1147      10.0              0.0   ...    4.0  192.857
            1148       5.0              0.0   ...    4.0  200.000
            1149      10.0              0.0   ...    4.0  200.000
            
            [1150 rows x 9 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 iterations and manually verifies the given constraints.

N_ITERATIONS = 3
for kIter in range(N_ITERATIONS):
    print(f"\n\n#### ITERATION {kIter+1} ####")

    print("## ASSERTS ##")
    print(
        "Number of entries with either Solvents C2 or C4 and a temperature above 151: ",
        (
            campaign.searchspace.discrete.exp_rep["Temp"].apply(lambda x: x > 151)
            & campaign.searchspace.discrete.exp_rep["Solv"].apply(
                lambda x: x in ["C2", "C4"]
            )
        ).sum(),
    )
    print(
        "Number of entries with either Solvents C5 or C6 and a pressure above 5:      ",
        (
            campaign.searchspace.discrete.exp_rep["Pressure"].apply(lambda x: x > 5)
            & campaign.searchspace.discrete.exp_rep["Solv"].apply(
                lambda x: x in ["C5", "C6"]
            )
        ).sum(),
    )
    print(
        "Number of entries with pressure below 3 and temperature above 120:           ",
        (
            campaign.searchspace.discrete.exp_rep["Pressure"].apply(lambda x: x < 3)
            & campaign.searchspace.discrete.exp_rep["Temp"].apply(lambda x: x > 120)
        ).sum(),
    )

    rec = campaign.recommend(batch_size=5)
    add_fake_results(rec, campaign.targets)
    campaign.add_measurements(rec)
#### ITERATION 1 ####
## ASSERTS ##
Number of entries with either Solvents C2 or C4 and a temperature above 151:  0
Number of entries with either Solvents C5 or C6 and a pressure above 5:       0
Number of entries with pressure below 3 and temperature above 120:            0


#### ITERATION 2 ####
## ASSERTS ##
Number of entries with either Solvents C2 or C4 and a temperature above 151:  0
Number of entries with either Solvents C5 or C6 and a pressure above 5:       0
Number of entries with pressure below 3 and temperature above 120:            0




#### ITERATION 3 ####
## ASSERTS ##
Number of entries with either Solvents C2 or C4 and a temperature above 151:  0
Number of entries with either Solvents C5 or C6 and a pressure above 5:       0
Number of entries with pressure below 3 and temperature above 120:            0