Introduction (Himmelblau’s function)

Let’s use blop to minimize Himmelblau’s function, which has four global minima:

[1]:
from blop.utils import prepare_re_env

%run -i $prepare_re_env.__file__ --db-type=temp
[2]:
import numpy as np
import matplotlib as mpl
from matplotlib import pyplot as plt
from blop.utils import functions

x1 = x2 = np.linspace(-6, 6, 256)
X1, X2 = np.meshgrid(x1, x2)

F = functions.himmelblau(X1, X2)

plt.pcolormesh(x1, x2, F, norm=mpl.colors.LogNorm(vmin=1e-1, vmax=1e3), cmap="magma_r")
plt.colorbar()
plt.xlabel("x1")
plt.ylabel("x2")
[2]:
Text(0, 0.5, 'x2')
../_images/tutorials_introduction_3_1.png

There are several things that our agent will need. The first ingredient is some degrees of freedom (these are always ophyd devices) which the agent will move around to different inputs within each DOF’s bounds (the second ingredient). We define these here:

[3]:
from blop import DOF

dofs = [
    DOF(name="x1", search_domain=(-6, 6)),
    DOF(name="x2", search_domain=(-6, 6)),
]

We also need to give the agent something to do. We want our agent to look in the feedback for a variable called ‘himmelblau’, and try to minimize it.

[4]:
from blop import Objective

objectives = [Objective(name="himmelblau", description="Himmeblau's function", target="min")]

In our digestion function, we define our objective as a deterministic function of the inputs:

[5]:
def digestion(df):
    for index, entry in df.iterrows():
        df.loc[index, "himmelblau"] = functions.himmelblau(entry.x1, entry.x2)

    return df

We then combine these ingredients into an agent, giving it an instance of databroker so that it can see the output of the plans it runs.

[6]:
from blop import Agent

agent = Agent(
    dofs=dofs,
    objectives=objectives,
    digestion=digestion,
    db=db,
)

Without any data, we can’t make any inferences about what the function looks like, and so we can’t use any non-trivial acquisition functions. Let’s start by quasi-randomly sampling the parameter space, and plotting our model of the function:

[7]:
RE(agent.learn("quasi-random", n=36))
agent.plot_objectives()


Transient Scan ID: 1     Time: 2024-05-13 18:01:32
Persistent Unique Scan ID: '65fa592d-feea-4533-a7f5-f504bf438258'
New stream: 'primary'
+-----------+------------+------------+------------+
|   seq_num |       time |         x1 |         x2 |
+-----------+------------+------------+------------+
|         1 | 18:01:32.0 |      0.597 |     -1.116 |
|         2 | 18:01:32.0 |      2.626 |     -2.395 |
|         3 | 18:01:32.0 |      3.041 |     -3.209 |
|         4 | 18:01:32.0 |      0.970 |     -3.982 |
|         5 | 18:01:32.0 |      1.504 |     -5.261 |
|         6 | 18:01:32.0 |      0.208 |     -5.153 |
|         7 | 18:01:32.1 |     -0.337 |     -4.313 |
|         8 | 18:01:32.1 |     -2.416 |     -5.663 |
|         9 | 18:01:32.1 |     -4.874 |     -4.889 |
|        10 | 18:01:32.1 |     -3.903 |     -3.586 |
|        11 | 18:01:32.1 |     -2.044 |     -2.809 |
|        12 | 18:01:32.1 |     -1.459 |     -1.460 |
|        13 | 18:01:32.1 |     -3.531 |     -0.733 |
|        14 | 18:01:32.1 |     -5.996 |     -2.035 |
|        15 | 18:01:32.1 |     -5.211 |     -0.863 |
|        16 | 18:01:32.1 |     -5.365 |      0.131 |
|        17 | 18:01:32.1 |     -4.988 |      3.091 |
|        18 | 18:01:32.1 |     -4.447 |      4.742 |
|        19 | 18:01:32.1 |     -2.911 |      3.816 |
|        20 | 18:01:32.1 |     -3.319 |      1.782 |
|        21 | 18:01:32.1 |     -1.782 |      0.857 |
|        22 | 18:01:32.1 |     -0.878 |      2.555 |
|        23 | 18:01:32.1 |      0.053 |      2.928 |
|        24 | 18:01:32.1 |     -1.239 |      3.592 |
|        25 | 18:01:32.1 |     -0.501 |      5.514 |
|        26 | 18:01:32.1 |      1.181 |      5.875 |
|        27 | 18:01:32.1 |      2.135 |      4.201 |
|        28 | 18:01:32.1 |      3.622 |      5.148 |
|        29 | 18:01:32.1 |      5.718 |      3.427 |
|        30 | 18:01:32.1 |      5.304 |      2.402 |
|        31 | 18:01:32.1 |      3.999 |      2.201 |
|        32 | 18:01:32.1 |      2.512 |      1.254 |
|        33 | 18:01:32.1 |      4.590 |      0.479 |
|        34 | 18:01:32.1 |      4.162 |     -0.344 |
|        35 | 18:01:32.1 |      5.084 |     -1.670 |
|        36 | 18:01:32.1 |      5.456 |     -4.535 |
+-----------+------------+------------+------------+
generator list_scan ['65fa592d'] (scan num: 1)



../_images/tutorials_introduction_13_1.png

To decide which points to sample, the agent needs an acquisition function. The available acquisition function are here:

[8]:
agent.all_acqfs
[8]:
identifier type multitask_only description
name
expected_improvement ei analytic False The expected value of max(f(x) - \nu, 0), wher...
expected_mean em analytic False The expected value at each input.
noisy_expected_hypervolume_improvement nehvi analytic True It's like a big box. How big is the box?
probability_of_improvement pi analytic False The probability that this input improves on th...
upper_confidence_bound ucb analytic False The expected value, plus some multiple of the ...
lower_bound_max_value_entropy lbmve monte_carlo False Max entropy search, basically
monte_carlo_expected_improvement qei monte_carlo False The expected value of max(f(x) - \nu, 0), wher...
monte_carlo_expected_mean qem monte_carlo False The expected value at each input.
monte_carlo_noisy_expected_hypervolume_improvement qnehvi monte_carlo True It's like a big box. How big is the box?
monte_carlo_probability_of_improvement qpi monte_carlo False The probability that this input improves on th...
monte_carlo_upper_confidence_bound qucb monte_carlo False The expected value, plus some multiple of the ...
grid g random False A grid scan over the parameters.
quasi-random qr random False Sobol-sampled quasi-random points.
random r random False Uniformly-sampled random points.

Now we can start to learn intelligently. Using the shorthand acquisition functions shown above, we can see the output of a few different ones:

[9]:
agent.plot_acquisition(acqf="qei")
../_images/tutorials_introduction_17_0.png

To decide where to go, the agent will find the inputs that maximize a given acquisition function:

[10]:
agent.ask("qei", n=1)
[10]:
{'points': {'x1': [2.5886143716458356], 'x2': [2.5868893591517974]},
 'acqf_name': 'monte_carlo_expected_improvement',
 'acqf_obj': [45.28603303746758],
 'acqf_kwargs': {'best_f': -4.25805406365225},
 'duration_ms': 150.02220399998123,
 'sequential': True,
 'upsample': 1,
 'read_only_values': array([], shape=(1, 0), dtype=float64)}

We can also ask the agent for multiple points to sample and it will jointly maximize the acquisition function over all sets of inputs, and find the most efficient route between them:

[11]:
res = agent.ask("qei", n=8, route=True)
agent.plot_acquisition(acqf="qei")
plt.scatter(res["points"]["x1"], res["points"]["x2"], marker="d", facecolor="w", edgecolor="k")
plt.plot(res["points"]["x1"], res["points"]["x2"], color="r")
[11]:
[<matplotlib.lines.Line2D at 0x7efd184367a0>]
../_images/tutorials_introduction_21_1.png

All of this is automated inside the learn method, which will find a point (or points) to sample, sample them, and retrain the model and its hyperparameters with the new data. To do 4 learning iterations of 8 points each, we can run

[12]:
RE(agent.learn("qei", n=4, iterations=8))


Transient Scan ID: 2     Time: 2024-05-13 18:01:41
Persistent Unique Scan ID: '4acfaa28-7aad-490a-8b1e-baf4a9c94157'
New stream: 'primary'
+-----------+------------+------------+------------+
|   seq_num |       time |         x1 |         x2 |
+-----------+------------+------------+------------+
|         1 | 18:01:41.2 |      2.588 |      2.587 |
|         2 | 18:01:41.2 |      2.162 |      2.658 |
|         3 | 18:01:41.2 |     -2.573 |      2.928 |
|         4 | 18:01:41.2 |     -3.634 |     -2.713 |
+-----------+------------+------------+------------+
generator list_scan ['4acfaa28'] (scan num: 2)





Transient Scan ID: 3     Time: 2024-05-13 18:01:44
Persistent Unique Scan ID: '6fa50cf2-62b9-4296-bece-ecae07a75330'
New stream: 'primary'
+-----------+------------+------------+------------+
|   seq_num |       time |         x1 |         x2 |
+-----------+------------+------------+------------+
|         1 | 18:01:44.5 |      3.600 |     -1.852 |
|         2 | 18:01:44.5 |      3.180 |     -0.254 |
|         3 | 18:01:44.5 |      3.296 |      0.526 |
|         4 | 18:01:44.5 |      3.243 |      1.555 |
+-----------+------------+------------+------------+
generator list_scan ['6fa50cf2'] (scan num: 3)





Transient Scan ID: 4     Time: 2024-05-13 18:01:46
Persistent Unique Scan ID: '181ef828-9d9f-4625-b9e8-25da8a7e3fce'
New stream: 'primary'
+-----------+------------+------------+------------+
|   seq_num |       time |         x1 |         x2 |
+-----------+------------+------------+------------+
|         1 | 18:01:46.8 |      3.063 |      2.082 |
|         2 | 18:01:46.8 |      3.516 |     -1.345 |
|         3 | 18:01:46.8 |     -3.622 |     -3.281 |
|         4 | 18:01:46.8 |     -3.043 |      3.097 |
+-----------+------------+------------+------------+
generator list_scan ['181ef828'] (scan num: 4)





Transient Scan ID: 5     Time: 2024-05-13 18:01:49
Persistent Unique Scan ID: '9ef8f41c-0f6e-4aa3-a8b8-28ff75a880a0'
New stream: 'primary'
+-----------+------------+------------+------------+
|   seq_num |       time |         x1 |         x2 |
+-----------+------------+------------+------------+
|         1 | 18:01:49.4 |     -2.741 |      3.210 |
|         2 | 18:01:49.4 |      3.713 |     -1.655 |
|         3 | 18:01:49.4 |      3.460 |     -2.064 |
|         4 | 18:01:49.4 |      3.625 |     -2.299 |
+-----------+------------+------------+------------+
generator list_scan ['9ef8f41c'] (scan num: 5)





Transient Scan ID: 6     Time: 2024-05-13 18:01:50
Persistent Unique Scan ID: 'a161a07e-9240-41fe-83ca-693eab7721f2'
New stream: 'primary'
+-----------+------------+------------+------------+
|   seq_num |       time |         x1 |         x2 |
+-----------+------------+------------+------------+
|         1 | 18:01:50.8 |      3.610 |     -1.915 |
|         2 | 18:01:50.8 |      2.932 |      1.927 |
|         3 | 18:01:50.8 |     -3.848 |     -3.247 |
|         4 | 18:01:50.8 |     -3.790 |     -3.285 |
+-----------+------------+------------+------------+
generator list_scan ['a161a07e'] (scan num: 6)





Transient Scan ID: 7     Time: 2024-05-13 18:01:53
Persistent Unique Scan ID: '8669cea8-3538-4cd6-a12f-8c454aef0c65'
New stream: 'primary'
+-----------+------------+------------+------------+
|   seq_num |       time |         x1 |         x2 |
+-----------+------------+------------+------------+
|         1 | 18:01:53.2 |     -2.823 |      3.077 |
|         2 | 18:01:53.2 |     -2.413 |      3.248 |
|         3 | 18:01:53.2 |      3.067 |      1.899 |
|         4 | 18:01:53.2 |      3.560 |     -1.771 |
+-----------+------------+------------+------------+
generator list_scan ['8669cea8'] (scan num: 7)





Transient Scan ID: 8     Time: 2024-05-13 18:01:54
Persistent Unique Scan ID: '1645507a-547b-4993-bf54-2d9d7e14b260'
New stream: 'primary'
+-----------+------------+------------+------------+
|   seq_num |       time |         x1 |         x2 |
+-----------+------------+------------+------------+
|         1 | 18:01:54.6 |      2.979 |      2.033 |
|         2 | 18:01:54.7 |      3.058 |      2.464 |
|         3 | 18:01:54.7 |      3.099 |      2.587 |
|         4 | 18:01:54.7 |     -3.773 |     -3.294 |
+-----------+------------+------------+------------+
generator list_scan ['1645507a'] (scan num: 8)





Transient Scan ID: 9     Time: 2024-05-13 18:01:55
Persistent Unique Scan ID: '53bf5515-9c53-4f94-846f-df615969e16f'
New stream: 'primary'
+-----------+------------+------------+------------+
|   seq_num |       time |         x1 |         x2 |
+-----------+------------+------------+------------+
|         1 | 18:01:55.7 |     -2.810 |      3.080 |
|         2 | 18:01:55.7 |     -2.817 |      3.136 |
|         3 | 18:01:55.7 |      2.477 |      1.147 |
|         4 | 18:01:55.7 |      3.007 |      1.995 |
+-----------+------------+------------+------------+
generator list_scan ['53bf5515'] (scan num: 9)



[12]:
('4acfaa28-7aad-490a-8b1e-baf4a9c94157',
 '6fa50cf2-62b9-4296-bece-ecae07a75330',
 '181ef828-9d9f-4625-b9e8-25da8a7e3fce',
 '9ef8f41c-0f6e-4aa3-a8b8-28ff75a880a0',
 'a161a07e-9240-41fe-83ca-693eab7721f2',
 '8669cea8-3538-4cd6-a12f-8c454aef0c65',
 '1645507a-547b-4993-bf54-2d9d7e14b260',
 '53bf5515-9c53-4f94-846f-df615969e16f')

Our agent has found all the global minima of Himmelblau’s function using Bayesian optimization, and we can ask it for the best point:

[13]:
agent.plot_objectives()
print(agent.best)
x1                                    3.006574
x2                                    1.994873
himmelblau                            0.001374
time             2024-05-13 18:01:55.765034199
acqf          monte_carlo_expected_improvement
Name: 67, dtype: object
../_images/tutorials_introduction_25_1.png