Unconstrained optimization - EGO#

[1]:
import numpy as np
from matplotlib import pyplot as plt

from smt_optim.benchmarks.registry import get_problem

from smt_optim.core import Driver, ObjectiveConfig, ConstraintConfig, DriverConfig, Problem

from smt_optim.surrogate_models.smt import SmtAutoModel

from smt_optim.acquisition_strategies import MFSEGO

Unconstrained 1D optimization#

This example will use the Sasena test function (2002). The bounds are \([0, 10]\)

[2]:
def sasena_2002(x: np.ndarray):
    return -np.sin(x) - np.exp(x / 100) + 10

bounds = np.array([[0, 10]])

[3]:

obj_config = ObjectiveConfig( objective=[sasena_2002], type="minimize", surrogate=SmtAutoModel, # set which GP to model the objective ) prob_definition = Problem( obj_configs=[obj_config], design_space=bounds, )

Once the problem is initialized, we can configure the optimization driver. We can set the configuration using the DriverConfig dataclass and then initialize the driver with the Driver class.

In our example, we set the following parameters:

  • max_iter corresponds to the maximum number of iteration that will be performed.

  • nt_init corresponds to the number of samples in the initial DOE. The initial DOE will be generated using LHS.

  • verbose can be set to True or False. If set to True some basic information will be printed at the end of each iteration.

  • seed allows to reproduce optimization run.

The MFSEGO optimization strategy must be pass to perform EGO (unconstrained optimization), SEGO (constrained optimization) and MFSEGO (unconstrained or constrained and multi-fidelity optimization).

[4]:
driver_config = DriverConfig(
    max_iter = 5,              # stopping criterion
    nt_init = 2,                # number of sample in the initial DoE
    verbose = True,
    seed=42,
)

# configure the optimizer. Note: if a single fidelity level is given to the MFSEGO acquisition strategy, it falls back to the SEGO framework.
driver = Driver(prob_definition, driver_config, MFSEGO)

We can launch the optimization using the optimize() class method. It will return a State object.

[5]:
# return the optimization data
state = driver.optimize()
          iter         budget           fmin       fidelity        gp_time       acq_time
             1              3    8.13516e+00              1          0.046          0.065
             2              4    8.11303e+00              1          0.127          0.067
             3              5    8.03882e+00              1          0.138          0.047
             4              6    7.91831e+00              1          0.093          0.047
             5              7    7.91826e+00              1          0.091          0.046

Once the optimization terminated, we can recover the best sample using the get_best_sample() class method. It will return a Sample dataclass with the lowest objective value.

[6]:
sample = state.get_best_sample()
sample
[6]:
======= sample data =======
x =             [7.85760188]
obj =           [7.91826097]
cstr =          []
eval_time =     [6.29399437e-06]
------- meta data -------
iter =     5
budget =     7
fidelity =     0
===========================

We can only also recover the entire DOE using the dataset attribute.

[7]:
dataset = state.dataset

data = dataset.export_as_dict()
xt = data["x"]
yt = data["obj"]

[8]:
x_test = np.linspace(0, 10, 201)
y_test = sasena_2002(x_test)

fig, ax = plt.subplots()
ax.plot(x_test, y_test)
ax.scatter(xt, yt)
ax.scatter(sample.x, sample.obj, 100, marker="*", color="C3", zorder=20, label=r"$f_\min$")

ax.legend()
plt.show()

../../_images/_collections_tutorial_ego_14_0.png

Unconstrained 2D optimization#

[9]:
def modified_branin(x):
    """
    Engineering Design via Surrogate Modelling: A Practical Guide
    A. I. J. Forrester, A. Sóbester and A. J. Keane
    2008
    """
    X1 = 15 * x[0] - 5
    X2 = 15 * x[1]

    a = 1
    b = 5.1 / (4 * np.pi ** 2)
    c = 5 / np.pi
    d = 6
    e = 10
    ff = 1 / (8 * np.pi)
    f = (a * (X2 - b * X1 ** 2 + c * X1 - d) ** 2 + e * (1 - ff) * np.cos(X1) + e) + 5 * x[0]

    return f


bounds = np.array([
    [0, 1],
    [0, 1],
])
[10]:
obj_config = ObjectiveConfig(
    [modified_branin],
    type="minimize",
    surrogate=SmtAutoModel,
)

prob_definition = Problem(
    obj_configs=[obj_config],
    design_space=bounds,            # problem bounds
)

opt_config = DriverConfig(
    max_iter = 20,
    nt_init = 3,
    verbose = True,
    scaling = True,
    seed=42,
)

driver = Driver(prob_definition, opt_config, MFSEGO)

state = driver.optimize()
          iter         budget           fmin       fidelity        gp_time       acq_time
             1              4    1.56584e+01              1          0.141          0.252
             2              5    1.55312e+01              1          0.289          0.488
             3              6    1.51473e+01              1          0.250          0.375
             4              7    1.42022e+01              1          0.271          0.340
             5              8    6.94080e+00              1          0.273          0.199
             6              9    6.94080e+00              1          0.236          0.251
             7             10    3.13434e+00              1          0.283          0.295
             8             11    1.85202e+00              1          0.257          0.381
             9             12    1.85202e+00              1          0.308          0.392
            10             13    1.85202e+00              1          0.316          0.496
          iter         budget           fmin       fidelity        gp_time       acq_time
            11             14    1.01441e+00              1          0.313          0.470
            12             15    1.01260e+00              1          0.345          0.464
            13             16    1.01260e+00              1          0.277          0.377
            14             17    1.01157e+00              1          0.299          0.546
            15             18    1.01157e+00              1          0.269          0.851
            16             19    1.01157e+00              1          0.244          0.948
            17             20    1.01157e+00              1          0.234          0.733
            18             21    1.01157e+00              1          0.211          0.856
            19             22    1.01157e+00              1          0.263          0.715
            20             23    1.01157e+00              1          0.250          0.822
[11]:
# get the best sample in the dataset
sample = state.get_best_sample()
xt = state.dataset.export_as_dict()["x"]

X = np.linspace(bounds[0, 0], bounds[0, 1], 201)
Y = np.linspace(bounds[1, 0], bounds[1, 1], 201)
XX, YY = np.meshgrid(X, Y)

data = np.vstack((XX.ravel(), YY.ravel())).T
z = np.empty(data.shape[0])

for i in range(data.shape[0]):
    z[i] = modified_branin(data[i, :])

Z = z.reshape(XX.shape)

fig, ax = plt.subplots()

ax.set_title("Modified Branin")

# plot the test function
ax.contour(XX, YY, Z, levels=20)

# plot the points in the final DoE
ax.scatter(xt[:, 0], xt[:, 1], 10, color="C7", alpha=0.75, label="DoE")

# plot the best sample in the final DoE
ax.scatter(sample.x[0], sample.x[1], 50, c="C3", marker="*", label=r"$f_{min}$", zorder=30)

ax.set_xlabel(r"$x_1$")
ax.set_ylabel(r"$x_2$")

ax.legend()
ax.set_aspect("equal")
plt.show()
../../_images/_collections_tutorial_ego_18_0.png