2. Black-Box Function¶

In black-box optimization, you aim to find a set of input values to an objective function to minimize the output value from the function. Here, the objective function is often a black-box function.

A black-box function is a function whose shape or gradient is unknown. It can be complex numerical simulations and measurements with/without postprocessing. Therefore, typical optimization methods of gradient descent and mathematical optimization cannot be used since you have no knowledge about the gradient or formulation of the function.

Here are some examples of preparing a black-box function for optimization.

Note

If your problem is a maximization problem, multiply your black-box function return value by -1 to convert the maximization problem to a minimization problem.

Examples of (black-box) objective function¶

Numerical simulations¶

You want to design an optimal wing profile to maximize the lift and minimize the drag. In this case, your black-box function would be a fluid flow simulation that returns the negative lift-drag ratio of a given wing profile setting. Your black-box function would look something like this:

from utils.pseudo_simulators import (
    pseudo_wing_simulator as wing_simulator,
)


def objective_lift_drag(
    wing_width: float,
    wing_height: float,
    wing_angle: float,
) -> 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

Experimental measurements¶

You want to design a “meta”-material to minimize the permittivity by synthesizing several from 10 raw materials. In this case, your black-box function would be an experiment that measures the permittivity of the meta-material created by synthesizing the chosen raw materials.

def objective_permittivity(choice: list[bool]) -> float:
    """This black-box function takes a choice of raw materials.

    The variable "choice" is a list of bool to express the selection of
    i-th raw material. The user is expected to perform material synthesis
    based on the given choice and enter the measured permittivity, which is
    then returned from this function to the optimizer.
    """
    message = (
        f"Synthesize based on {choice=} and enter the measured permittivity"
    )
    permittivity = input(message)
    return float(permittivity)  # value to minimize

Inverse problems¶

You can also use black-box optimization for inverse problems of complex phenomena.

For example, there is an output of a complex simulation, but you do not know its input parameters to reproduce the same output (target output). In this case, your black-box function would be a function that returns the difference (distance) between the target output and the simulation output based on the new “potentially-optimal” input.

import math

from utils.pseudo_simulators import (
    pseudo_complex_calculation as complex_calculation,
)

target_output = (5.0, 6.0, 6.0)


def objective_distance(x0: bool, x1: int, x2: float) -> float:
    """This black-box function takes a set of input (x0, x1, x2), executes
    complex_calculation(), and returns the distance between the obtained output
    and the target output.
    """
    output = complex_calculation(x0, x1, x2)
    return math.dist(target_output, output)  # value to minimize

Note

A more general and friendlier example of an inverse problem is Coca-Cola’s secret formula. In this case, your decision variables may be amounts of \(N_I\) number of ingredients in the syrup and \(N_C\) number of processing conditions. Your objective function would be an experiment where 100 testers taste the syrup based on the new “potentially optimal” formula and rate how close the taste is to the actual Coca-Cola (0 is the closest, 10 is the farthest). The return value could be the average of 100 testers’ ratings. The optimizer tries to find a formula for which the average rating is almost the smallest.

Associating variables with black-box function¶

Since the input to your black-box function and decision variables must have a one-to-one relation, it would be convenient to associate the variables and function closely. In Amplify-BBOpt, the blackbox decorator helps you associate the decision variables with your black-box function in different ways. Using the black-box function example mentioned in the numerical simulation above, here are some examples.

Method 1¶

Use the @blackbox decorator and define decision variables as input arguments’ default values.

from amplify_bbopt import RealVariable, blackbox

from utils.pseudo_simulators import (
    pseudo_wing_simulator as wing_simulator,
)


@blackbox
def objective_lift_drag(
    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

Method 2¶

Use the @blackbox decorator and define decision variables with Annotated.

from typing import Annotated

from amplify_bbopt import RealVariable, blackbox

from utils.pseudo_simulators import (
    pseudo_wing_simulator as wing_simulator,
)


@blackbox
def objective_lift_drag(
    wing_width: Annotated[float, RealVariable(bounds=(1, 20), nbins=100)],
    wing_height: Annotated[float, RealVariable(bounds=(1, 5), nbins=20)],
    wing_angle: Annotated[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

Attributes of the BlackBoxFuncBase class¶

The black-box function constructed in the above methods 1 and 2 using @blackbox decorator is also an instance of “black-box function class” inheriting BlackBoxFuncBase. The class has the following attributes.

Attribute

Data type

Details

variables

Variables

A class handles all the set variables with different types

constraints

Constraints

A class handles all the set constraints

name

str

The name of an black-box objective function

We will describe constraints next, so do not worry if you are unsure now.

Via the variables attribute you can access individual variables you set along with the black-box function. For example, for the above class instance objective_lift_drag (for any of the above methods), you can access the variables wing_width, wing_height and wing_angle as follows (for variable’s attributes see “1. Decision Variable”):

wing_width = objective_lift_drag.variables.wing_width

print(f"{wing_width.type=}")
print(f"{wing_width.bounds=}")
print(f"{wing_width.method=}")
wing_width.type=<class 'float'>
wing_width.bounds=(1, 20)
wing_width.method='domain_wall'

The variables attribute is also useful for obtaining information about the variables at once. For example, for the above class instance objective_lift_drag (for any of the above methods), the following line outputs information about all the variables associated with your black-box function.

print(objective_lift_drag.variables)
              type  nbins  len       method  nvars  i=0                 i=1                 i=2                 i=3                 i=4                 i=5                 i=6                 i=7                 i=8                i=9                i=10                i=11                i=12                i=13                i=14                i=15               i=16                i=17               i=18               i=19               i=20              i=21               i=22                i=23                i=24               i=25              i=26               i=27               i=28               i=29               i=30               i=31               i=32               i=33               i=34               i=35               i=36             i=37               i=38               i=39               i=40               i=41              i=42               i=43               i=44               i=45               i=46                i=47                i=48  \
wing_width   float    100    1  domain_wall     99  1.0  1.1919191919191918  1.3838383838383839  1.5757575757575757  1.7676767676767677  1.9595959595959596  2.1515151515151514  2.3434343434343434  2.5353535353535355  2.727272727272727   2.919191919191919   3.111111111111111  3.3030303030303028   3.494949494949495   3.686868686868687  3.8787878787878785  4.070707070707071   4.262626262626263  4.454545454545454  4.646464646464646  4.838383838383838  5.03030303030303  5.222222222222222  5.4141414141414135  5.6060606060606055  5.797979797979798  5.98989898989899  6.181818181818182  6.373737373737374  6.565656565656565  6.757575757575757  6.949494949494949  7.141414141414141  7.333333333333333  7.525252525252525  7.717171717171717  7.909090909090908  8.1010101010101  8.292929292929292  8.484848484848484  8.676767676767676  8.868686868686869  9.06060606060606  9.252525252525253  9.444444444444445  9.636363636363635  9.828282828282827  10.020202020202019  10.212121212121211   
wing_height  float     20    1  domain_wall     19  1.0  1.2105263157894737  1.4210526315789473   1.631578947368421  1.8421052631578947   2.052631578947368   2.263157894736842   2.473684210526316  2.6842105263157894  2.894736842105263  3.1052631578947367  3.3157894736842106   3.526315789473684  3.7368421052631575  3.9473684210526314   4.157894736842105  4.368421052631579   4.578947368421052  4.789473684210526                5.0                  -                 -                  -                   -                   -                  -                 -                  -                  -                  -                  -                  -                  -                  -                  -                  -                  -                -                  -                  -                  -                  -                 -                  -                  -                  -                  -                   -                   -   
wing_angle   float     20    1  domain_wall     19  0.0  2.3684210526315788  4.7368421052631575   7.105263157894736   9.473684210526315  11.842105263157894  14.210526315789473   16.57894736842105   18.94736842105263  21.31578947368421  23.684210526315788  26.052631578947366  28.421052631578945  30.789473684210524    33.1578947368421  35.526315789473685  37.89473684210526  40.263157894736835  42.63157894736842               45.0                  -                 -                  -                   -                   -                  -                 -                  -                  -                  -                  -                  -                  -                  -                  -                  -                  -                -                  -                  -                  -                  -                 -                  -                  -                  -                  -                   -                   -   
                           i=49                i=50                i=51               i=52                i=53                i=54                i=55                i=56               i=57               i=58                i=59                i=60                i=61                i=62               i=63                i=64                i=65                i=66                i=67               i=68                i=69                i=70                i=71                i=72                i=73              i=74                i=75                i=76                i=77                i=78               i=79                i=80                i=81                i=82                i=83               i=84               i=85                i=86                i=87               i=88               i=89               i=90                i=91                i=92                i=93                i=94                i=95                i=96                i=97  \
wing_width   10.404040404040403  10.595959595959595  10.787878787878787  10.97979797979798  11.171717171717171  11.363636363636363  11.555555555555555  11.747474747474747  11.93939393939394  12.13131313131313  12.323232323232322  12.515151515151514  12.707070707070706  12.898989898989898  13.09090909090909  13.282828282828282  13.474747474747474  13.666666666666666  13.858585858585858  14.05050505050505  14.242424242424242  14.434343434343434  14.626262626262625  14.818181818181817  15.010101010101009  15.2020202020202  15.393939393939393  15.585858585858585  15.777777777777777  15.969696969696969  16.16161616161616  16.353535353535353  16.545454545454547  16.737373737373737  16.929292929292927  17.12121212121212  17.31313131313131  17.505050505050505  17.696969696969695  17.88888888888889  18.08080808080808  18.27272727272727  18.464646464646464  18.656565656565654  18.848484848484848  19.040404040404038  19.232323232323232  19.424242424242422  19.616161616161616   
wing_height                   -                   -                   -                  -                   -                   -                   -                   -                  -                  -                   -                   -                   -                   -                  -                   -                   -                   -                   -                  -                   -                   -                   -                   -                   -                 -                   -                   -                   -                   -                  -                   -                   -                   -                   -                  -                  -                   -                   -                  -                  -                  -                   -                   -                   -                   -                   -                   -                   -   
wing_angle                    -                   -                   -                  -                   -                   -                   -                   -                  -                  -                   -                   -                   -                   -                  -                   -                   -                   -                   -                  -                   -                   -                   -                   -                   -                 -                   -                   -                   -                   -                  -                   -                   -                   -                   -                  -                  -                   -                   -                  -                  -                  -                   -                   -                   -                   -                   -                   -                   -   
                           i=98  i=99  
wing_width   19.808080808080806  20.0  
wing_height                   -     -  
wing_angle                    -     -  

The output shows the following information for each of the defined variables. You can see that each non-binary variable ranges and is discretized as described above.

  • value type (type),

  • number of discretization points (nbins)

  • length (len)

  • encoding method (method)[1]

  • number of the Amplify-SDK’s variables (nvars)[1]

  • discretization (value at each discretization point \(i\) )

A similar human-readable string representation is prepared for objective_lift_drag.constraints. We will describe this next.