Overview

Attention

Amplify-BBOpt is currently in version 0.x.x and is actively under development. The API and functionality are subject to change as the software has yet to reach a stable release. Users should be aware that breaking changes or modifications may occur without prior notification.

Black-box optimization

In mathematical optimization, the objective is to find a set of decision variables \(\boldsymbol{x}\) such that some objective function \(y = f(\boldsymbol{x})\) is minimized (or maximized). Here, \(\boldsymbol{x}\) is a decision variable vector with size \(d\), and each element takes a binary, integer, or real value.

\[\begin{split} \begin{aligned}     \mathrm{Minimize}&\,\,f(\boldsymbol{x}) \\     \mathrm{subject\,\,to\,\,}&\boldsymbol{x} \in \mathbb{R}^d, \text{where } x_i \in \{0, 1\}, \mathbb{Z}, \text{ or } \mathbb{R} \end{aligned} \end{split}\]

Here, if information about the objective function \(f(\boldsymbol{x})\) (functional form, gradient, submodularity, convexity, etc.) is given, you can perform efficient optimization. On the other hand, in the case of optimization to minimize (or maximize) values obtained by simulation or experiment for physical or social phenomena, the objective function \(f(\boldsymbol{x})\) corresponds to simulation or experiment, and you cannot describe the function explicitly. Mathematical optimization for such an unknown objective function \(f(\boldsymbol{x})\) is called black-box optimization.

In addition, evaluating such an objective function (running simulations or experiments) is usually relatively expensive (in terms of time and money). Therefore, even if the set of decision variables is finite, optimization by full search is generally difficult. Hence, an optimization method with as few objective function evaluations as possible is required.

Typical flow of black-box optimization

In black-box optimization, a serial optimization method is usually applied. The method iterates through the cycles shown in the figure below, simultaneously searching for both a good approximation model of the black-box function and the input that yields the minimum value of the approximation model.

Here is a basic flowchart outlining the typical steps involved in black-box optimization:

image

Details of the necessary steps to perform such black-box optimization are described later in the documents, but typically, you need to determine and prepare the following:

  1. Black-box objective function and decision variables,

  2. Initial training data.

Amplify-BBOpt takes care of the rest of the optimization procedures.

Typical Amplify-BBOpt usage

Below is a typical usage of Amplify-BBOpt. The code below determines the input values to minimize the output from our black-box function my_blackbox_func() by repeating the above black-box optimization cycles five times.

The black-box function returns the negative lift-drag ratio of a wing, computed by a pseudo simulator named pseudo_wing_simulator(), which is black box and can be viewed below (although it is unnecessary to see the logic as it is treated as black box here):

A black-box function generally returns a result from much more complex numerical simulations and experimental measurements.

from datetime import timedelta

from amplify import FixstarsClient
from amplify_bbopt import (
    DatasetGenerator,
    KernelQAOptimizer,
    RealVariable,
    blackbox,
)

from utils.pseudo_simulators import (
    pseudo_wing_simulator as wing_simulator,
)


# Create a black-box function with decision variables
@blackbox
def my_blackbox_func(
    wing_width: float = RealVariable(bounds=(1, 20), nbins=100),
    wing_height: float = RealVariable(bounds=(1, 5), nbins=20),
    wing_angle: float = RealVariable(bounds=(0, 45), nbins=20),
) -> float:
    """This black-box function executes wing_simulator() and
    returns the negative lift-drag ratio for a given wing's
    width, height, and angle.
    """
    lift, drag = wing_simulator(wing_width, wing_height, wing_angle)
    return -lift / drag  # value to minimize


# Generate initial training data set
num_init_data = 10
data = DatasetGenerator(objective=my_blackbox_func).generate(
    num_samples=num_init_data
)

# Set up solver client
client = FixstarsClient()
client.parameters.timeout = timedelta(milliseconds=2000)  # 2 seconds
# client.token = "xxxxxxxxxxx"  # Enter your Amplify API token.

# Instantiate the FMQA optimizer
optimizer = KernelQAOptimizer(
    data=data, client=client, objective=my_blackbox_func
)

# Perform FMQA optimization for [num_cycles] cycles
optimizer.optimize(num_cycles=5)

# Print results
print(f"{optimizer.best_solution=}")  # Solution (optimal input)
print(f"{optimizer.best_objective=:.3e}")  # Objective function value
Hide code cell output
amplify-bbopt | 2024/10/04 07:11:50 | INFO | ----------------------------------------
amplify-bbopt | 2024/10/04 07:11:50 | INFO | #0/10 initial data for my_blackbox_func
amplify-bbopt | 2024/10/04 07:11:50 | INFO | ----------------------------------------
amplify-bbopt | 2024/10/04 07:11:50 | INFO | #1/10 initial data for my_blackbox_func
amplify-bbopt | 2024/10/04 07:11:50 | INFO | ----------------------------------------
amplify-bbopt | 2024/10/04 07:11:50 | INFO | #2/10 initial data for my_blackbox_func
amplify-bbopt | 2024/10/04 07:11:50 | INFO | ----------------------------------------
amplify-bbopt | 2024/10/04 07:11:50 | INFO | #3/10 initial data for my_blackbox_func
amplify-bbopt | 2024/10/04 07:11:50 | INFO | ----------------------------------------
amplify-bbopt | 2024/10/04 07:11:50 | INFO | #4/10 initial data for my_blackbox_func
amplify-bbopt | 2024/10/04 07:11:50 | INFO | ----------------------------------------
amplify-bbopt | 2024/10/04 07:11:50 | INFO | #5/10 initial data for my_blackbox_func
amplify-bbopt | 2024/10/04 07:11:50 | INFO | ----------------------------------------
amplify-bbopt | 2024/10/04 07:11:50 | INFO | #6/10 initial data for my_blackbox_func
amplify-bbopt | 2024/10/04 07:11:50 | INFO | ----------------------------------------
amplify-bbopt | 2024/10/04 07:11:50 | INFO | #7/10 initial data for my_blackbox_func
amplify-bbopt | 2024/10/04 07:11:50 | INFO | ----------------------------------------
amplify-bbopt | 2024/10/04 07:11:50 | INFO | #8/10 initial data for my_blackbox_func
amplify-bbopt | 2024/10/04 07:11:50 | INFO | ----------------------------------------
amplify-bbopt | 2024/10/04 07:11:50 | INFO | #9/10 initial data for my_blackbox_func
amplify-bbopt | 2024/10/04 07:11:50 | INFO | ----------------------------------------
amplify-bbopt | 2024/10/04 07:11:50 | INFO | #1/5 optimization cycle, constraint wt: 7.77e+00
amplify-bbopt | 2024/10/04 07:11:50 | INFO | model corrcoef: 1.000, beta: 0.0
amplify-bbopt | 2024/10/04 07:11:53 | INFO | num_iterations: 20
amplify-bbopt | 2024/10/04 07:11:53 | INFO | y_hat=-5.768e+00, best objective=-5.768e+00
amplify-bbopt | 2024/10/04 07:11:53 | INFO | ----------------------------------------
amplify-bbopt | 2024/10/04 07:11:53 | INFO | #2/5 optimization cycle, constraint wt: 1.15e+01
amplify-bbopt | 2024/10/04 07:11:53 | INFO | model corrcoef: 1.000, beta: 0.0
amplify-bbopt | 2024/10/04 07:11:58 | INFO | num_iterations: 20
amplify-bbopt | 2024/10/04 07:11:58 | INFO | y_hat=-5.857e+00, best objective=-5.857e+00
amplify-bbopt | 2024/10/04 07:11:58 | INFO | ----------------------------------------
amplify-bbopt | 2024/10/04 07:11:58 | INFO | #3/5 optimization cycle, constraint wt: 1.17e+01
amplify-bbopt | 2024/10/04 07:11:58 | INFO | model corrcoef: 1.000, beta: 0.0
amplify-bbopt | 2024/10/04 07:12:01 | INFO | num_iterations: 20
amplify-bbopt | 2024/10/04 07:12:01 | INFO | y_hat=-6.286e+00, best objective=-6.286e+00
amplify-bbopt | 2024/10/04 07:12:01 | INFO | ----------------------------------------
amplify-bbopt | 2024/10/04 07:12:01 | INFO | #4/5 optimization cycle, constraint wt: 1.26e+01
amplify-bbopt | 2024/10/04 07:12:01 | INFO | model corrcoef: 1.000, beta: 0.0
amplify-bbopt | 2024/10/04 07:12:04 | INFO | num_iterations: 20
amplify-bbopt | 2024/10/04 07:12:04 | INFO | modifying solution (11, is_frequent=False), {'wing_width': np.float64(20.0), 'wing_height': np.float64(2.263157894736842), 'wing_angle': np.float64(7.105263157894736)} --> {'wing_width': np.float64(17.12121212121212), 'wing_height': np.float64(1.631578947368421), 'wing_angle': np.float64(2.3684210526315788)}.
amplify-bbopt | 2024/10/04 07:12:04 | INFO | y_hat=-6.991e+00, best objective=-6.991e+00
amplify-bbopt | 2024/10/04 07:12:04 | INFO | ----------------------------------------
amplify-bbopt | 2024/10/04 07:12:04 | INFO | #5/5 optimization cycle, constraint wt: 1.40e+01
amplify-bbopt | 2024/10/04 07:12:04 | INFO | model corrcoef: 1.000, beta: 0.0
amplify-bbopt | 2024/10/04 07:12:06 | INFO | num_iterations: 21
amplify-bbopt | 2024/10/04 07:12:06 | INFO | y_hat=-7.054e+00, best objective=-7.054e+00
optimizer.best_solution={'wing_width': np.float64(17.12121212121212), 'wing_height': np.float64(1.631578947368421), 'wing_angle': np.float64(7.105263157894736)}
optimizer.best_objective=-7.054e+00
from amplify_bbopt import plot_history

plot_history(optimizer.fetch_history())  # Plot the optimization history