Multi-Objective Optimizer

Amplify-BBOpt provides an optimizer class called “MultiObjectiveOptimizer” that handle optimization problems with multiple black-box objective functions and constaints spanning these objective functions.

Here is an example using the objective functions, obj_1 and obj_2, from the previous page. As you can see in the various examples below, using MultiObjectiveOptimizer is not so different from using basic optimizers.

Basic usage

For initial training data, you can simply pass a list of all objective functions to DatasetGenerator to create initial training dataset. The additional constraints spanning different objective functions are taken care of as well during the data generation.

As for the optimizer, instantiate a basic optimizer for each of objective functions involed. This identifies various optimization settings specific to the black-box function, such as what surrogate model to use and what weight to impose for the black-box function. Once the basic optimizers are instantiated for the individual objective functions, pass them in a list to the MultiObjectiveOptimizer, along with other usual arguments such as client. Finally, you can execute the MultiObjectiveOptimizer.optimize method to perform black-box optimization with multiple-objective functions.

from datetime import timedelta

import numpy as np
from amplify import FixstarsClient
from amplify_bbopt import (
    BinaryVariable,
    DatasetGenerator,
    IntegerVariable,
    KernelQAOptimizer,
    MultiObjectiveOptimizer,
    blackbox,
    equal_to,
)

np.set_printoptions(legacy="1.25")

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


@blackbox
def obj_1(
    a: bool = BinaryVariable(),
    b: int = IntegerVariable(bounds=(1, 5)),
) -> int:
    print(f"{a=}, {b=}")
    if a:
        return -b
    return b


@blackbox
def obj_2(
    c: int = IntegerVariable(bounds=(1, 5)),
    d: int = IntegerVariable(bounds=(1, 5)),
) -> int:
    print(f"{c=}, {d=}")
    return c * d * d


my_constraint = equal_to(obj_1.variables.b - obj_2.variables.c, 0)

# You can add "my_constraint" to obj_1 instead.
obj_2.add_constraint(my_constraint)

data_1, data_2 = DatasetGenerator([obj_1, obj_2]).generate(num_samples=3)

optimizer_1 = KernelQAOptimizer(data_1, obj_1)
optimizer_2 = KernelQAOptimizer(data_2, obj_2)

multi_optimizer = MultiObjectiveOptimizer(
    [optimizer_1, optimizer_2], client=client
)
multi_optimizer.optimize(num_cycles=2)

print(f"{multi_optimizer.best_solution=}")  # Solution (optimal input)
print(f"{multi_optimizer.best_objective=:.3e}")  # Objective function value
Hide code cell output
amplify-bbopt | 2024/10/04 06:09:08 | INFO | ----------------------------------------
amplify-bbopt | 2024/10/04 06:09:08 | INFO | #0/3 initial data for obj_1
amplify-bbopt | 2024/10/04 06:09:08 | INFO | - [obj]: a=False, b=1
amplify-bbopt | 2024/10/04 06:09:08 | INFO | ----------------------------------------
amplify-bbopt | 2024/10/04 06:09:08 | INFO | #1/3 initial data for obj_1
amplify-bbopt | 2024/10/04 06:09:08 | INFO | - [obj]: a=True, b=3
amplify-bbopt | 2024/10/04 06:09:08 | INFO | ----------------------------------------
amplify-bbopt | 2024/10/04 06:09:08 | INFO | #2/3 initial data for obj_1
amplify-bbopt | 2024/10/04 06:09:08 | INFO | - [obj]: a=False, b=4
amplify-bbopt | 2024/10/04 06:09:08 | INFO | ----------------------------------------
amplify-bbopt | 2024/10/04 06:09:08 | INFO | #0/3 initial data for obj_2
amplify-bbopt | 2024/10/04 06:09:08 | INFO | - [obj]: c=1, d=1
amplify-bbopt | 2024/10/04 06:09:08 | INFO | ----------------------------------------
amplify-bbopt | 2024/10/04 06:09:08 | INFO | #1/3 initial data for obj_2
amplify-bbopt | 2024/10/04 06:09:08 | INFO | - [obj]: c=3, d=5
amplify-bbopt | 2024/10/04 06:09:08 | INFO | ----------------------------------------
amplify-bbopt | 2024/10/04 06:09:08 | INFO | #2/3 initial data for obj_2
amplify-bbopt | 2024/10/04 06:09:08 | INFO | - [obj]: c=4, d=2
amplify-bbopt | 2024/10/04 06:09:08 | INFO | ----------------------------------------
amplify-bbopt | 2024/10/04 06:09:08 | INFO | #1/2 optimization cycle, constraint wt: 1.50e+02
amplify-bbopt | 2024/10/04 06:09:08 | INFO | model corrcoef: 0.985, beta: 0.0
amplify-bbopt | 2024/10/04 06:09:08 | INFO | model corrcoef: 1.000, beta: 0.0
2024-10-04 06:09:10 [amplify:WARNING] Connection timeout, retrying after sleep 1 second (1/3)
amplify-bbopt | 2024/10/04 06:09:14 | INFO | - [obj]: a=True, b=1
amplify-bbopt | 2024/10/04 06:09:14 | INFO | obj_1: -1.00000e+00 (wt: 1.000e+00)
amplify-bbopt | 2024/10/04 06:09:14 | INFO | - [obj]: c=1, d=1
amplify-bbopt | 2024/10/04 06:09:14 | INFO | obj_2: 1.00000e+00 (wt: 1.000e+00)
amplify-bbopt | 2024/10/04 06:09:14 | INFO | y_hat_total=0.000e+00, y_best=0.000e+00
amplify-bbopt | 2024/10/04 06:09:14 | INFO | ----------------------------------------
amplify-bbopt | 2024/10/04 06:09:14 | INFO | #2/2 optimization cycle, constraint wt: 1.50e+02
amplify-bbopt | 2024/10/04 06:09:14 | INFO | model corrcoef: 0.984, beta: 0.0
amplify-bbopt | 2024/10/04 06:09:14 | INFO | model corrcoef: 1.000, beta: 0.0
2024-10-04 06:09:15 [amplify:WARNING] Connection timeout, retrying after sleep 1 second (1/3)
amplify-bbopt | 2024/10/04 06:09:19 | INFO | identical input was obtained (0), x_hat={'a': True, 'b': 1, 'c': 1, 'd': 1}.
amplify-bbopt | 2024/10/04 06:09:19 | INFO | - [obj]: a=True, b=1
amplify-bbopt | 2024/10/04 06:09:19 | INFO | obj_1: -1.00000e+00 (wt: 1.000e+00)
amplify-bbopt | 2024/10/04 06:09:19 | INFO | - [obj]: c=1, d=2
amplify-bbopt | 2024/10/04 06:09:19 | INFO | obj_2: 4.00000e+00 (wt: 1.000e+00)
amplify-bbopt | 2024/10/04 06:09:19 | INFO | y_hat_total=3.000e+00, y_best=0.000e+00
multi_optimizer.best_solution={'a': True, 'b': 1, 'c': 1, 'd': 1}
multi_optimizer.best_objective=0.000e+00

Note

Here is more detailed description of what happens when you pass a list of objective functions [obj_1, obj_2] to DatasetGenerator or a list of basic optimizers [optimizer_1, optimizer_2] to MultiObjectiveOptimizer. These two classes will instantiate the BlackBoxFuncList class with all the objective functions involved. The BlackBoxFuncList knows not only all the objective functions, but also constraints, how they should be modelled, numerous others. During its instantiation process, the BlackBoxFuncList class tries to unify the variables across the objective functions – the variables with the same name in the different objective functions are unified into one variable with the same attributes. Then the class updates the constraints accordingly. Internally, both DatasetGenerator and MultiObjectiveOptimizer work towards the resulting BlackBoxFuncList class instance.

That means that it does not matter whether you add_constraint the my_constraint to obj_1 or obj_2, as long as the constraint is added one of the objective functions.

Using different surrogate/acquisition models

You can use different surrogate/acquisition models for different objective functions. To do so, simply instantiate different basic optimizers with different model/trainer settings. Here is an example based on the above code snippet.

from amplify_bbopt import FMQAOptimizer

# obj_1 is modeled by kernel-based model as before
optimizer_1 = KernelQAOptimizer(data_1, obj_1)

# Now obj_2 is modeled by FM
optimizer_2 = FMQAOptimizer(data_2, obj_2)
optimizer_2.trainer.set_model_params(k=20)
optimizer_2.trainer.set_train_params(epochs=1000)

multi_optimizer = MultiObjectiveOptimizer(
    [optimizer_1, optimizer_2], client=client
)

multi_optimizer.optimize(num_cycles=2)

print(f"{multi_optimizer.best_solution=}")  # Solution (optimal input)
print(f"{multi_optimizer.best_objective=:.3e}")  # Objective function value
Hide code cell output
amplify-bbopt | 2024/10/04 06:09:19 | INFO | ----------------------------------------
amplify-bbopt | 2024/10/04 06:09:19 | INFO | #1/2 optimization cycle, constraint wt: 1.50e+02
amplify-bbopt | 2024/10/04 06:09:19 | INFO | model corrcoef: 0.985, beta: 0.0
amplify-bbopt | 2024/10/04 06:09:20 | WARNING | No data split is performed for this cycle.
amplify-bbopt | 2024/10/04 06:09:21 | INFO | model corrcoef: 1.000
2024-10-04 06:09:22 [amplify:WARNING] Connection timeout, retrying after sleep 1 second (1/3)
amplify-bbopt | 2024/10/04 06:09:27 | INFO | - [obj]: a=True, b=2
amplify-bbopt | 2024/10/04 06:09:27 | INFO | obj_1: -2.00000e+00 (wt: 1.000e+00)
amplify-bbopt | 2024/10/04 06:09:27 | INFO | - [obj]: c=2, d=1
amplify-bbopt | 2024/10/04 06:09:27 | INFO | obj_2: 2.00000e+00 (wt: 1.000e+00)
amplify-bbopt | 2024/10/04 06:09:27 | INFO | y_hat_total=0.000e+00, y_best=0.000e+00
amplify-bbopt | 2024/10/04 06:09:27 | INFO | ----------------------------------------
amplify-bbopt | 2024/10/04 06:09:27 | INFO | #2/2 optimization cycle, constraint wt: 1.50e+02
amplify-bbopt | 2024/10/04 06:09:27 | INFO | model corrcoef: 0.988, beta: 0.0
amplify-bbopt | 2024/10/04 06:09:27 | WARNING | No data split is performed for this cycle.
amplify-bbopt | 2024/10/04 06:09:28 | INFO | model corrcoef: 1.000
amplify-bbopt | 2024/10/04 06:09:31 | INFO | identical input was obtained (0), x_hat={'a': True, 'b': 2, 'c': 2, 'd': 1}.
amplify-bbopt | 2024/10/04 06:09:31 | INFO | - [obj]: a=True, b=2
amplify-bbopt | 2024/10/04 06:09:31 | INFO | obj_1: -2.00000e+00 (wt: 1.000e+00)
amplify-bbopt | 2024/10/04 06:09:31 | INFO | - [obj]: c=2, d=2
amplify-bbopt | 2024/10/04 06:09:31 | INFO | obj_2: 8.00000e+00 (wt: 1.000e+00)
amplify-bbopt | 2024/10/04 06:09:31 | INFO | y_hat_total=6.000e+00, y_best=0.000e+00
multi_optimizer.best_solution={'a': True, 'b': 2, 'c': 2, 'd': 1}
multi_optimizer.best_objective=0.000e+00

Weighting for objectives

Weigiting objective functions is useful when you have certain objective functions to optimize more than the other objective functions. Also, appropriately weighting generally results in better optimization result, especially when the output value ranges from objective functions have relatively large discrepancies. In such cases, it may be a good idea to add a larger weight for the objective functions that return relatively small values overall. In multiple-objective optimization, the weight is only multiplied to the part of the optimization model (amplify.Model). For the output values from any objective functions, the weight will not be multiplied.

By default, the weights for all objectives are set as 1.

Setting a constant weight

Here is an example to use different weights for objective functions. Again, we use the same obj_1 and obj_2. As you can see the definitions, the value ranges of obj_1 is \(\pm5\) and that for obj_2 is between 1 and 25. Thus, let us give a larger weight for obj_1.

optimizer_1 = KernelQAOptimizer(data_1, obj_1, objective_weight=10)
optimizer_2 = KernelQAOptimizer(data_2, obj_2)

multi_optimizer = MultiObjectiveOptimizer(
    [optimizer_1, optimizer_2], client=client
)

multi_optimizer.optimize(num_cycles=2)

print(f"{multi_optimizer.best_solution=}")  # Solution (optimal input)
print(f"{multi_optimizer.best_objective=:.3e}")  # Objective function value
Hide code cell output
amplify-bbopt | 2024/10/04 06:09:31 | INFO | ----------------------------------------
amplify-bbopt | 2024/10/04 06:09:31 | INFO | #1/2 optimization cycle, constraint wt: 1.50e+02
amplify-bbopt | 2024/10/04 06:09:31 | INFO | model corrcoef: 0.985, beta: 0.0
amplify-bbopt | 2024/10/04 06:09:31 | INFO | model corrcoef: 1.000, beta: 0.0
amplify-bbopt | 2024/10/04 06:09:34 | INFO | - [obj]: a=True, b=3
amplify-bbopt | 2024/10/04 06:09:34 | INFO | obj_1: -3.00000e+00 (wt: 1.000e+01)
amplify-bbopt | 2024/10/04 06:09:34 | INFO | - [obj]: c=3, d=1
amplify-bbopt | 2024/10/04 06:09:34 | INFO | obj_2: 3.00000e+00 (wt: 1.000e+00)
amplify-bbopt | 2024/10/04 06:09:34 | INFO | y_hat_total=0.000e+00, y_best=0.000e+00
amplify-bbopt | 2024/10/04 06:09:34 | INFO | ----------------------------------------
amplify-bbopt | 2024/10/04 06:09:34 | INFO | #2/2 optimization cycle, constraint wt: 1.50e+02
amplify-bbopt | 2024/10/04 06:09:34 | INFO | model corrcoef: 0.992, beta: 0.0
amplify-bbopt | 2024/10/04 06:09:34 | INFO | model corrcoef: 1.000, beta: 0.0
2024-10-04 06:09:35 [amplify:WARNING] Connection timeout, retrying after sleep 1 second (1/3)
amplify-bbopt | 2024/10/04 06:09:40 | INFO | identical input was obtained (0), x_hat={'a': True, 'b': 3, 'c': 3, 'd': 1}.
amplify-bbopt | 2024/10/04 06:09:40 | INFO | - [obj]: a=True, b=3
amplify-bbopt | 2024/10/04 06:09:40 | INFO | obj_1: -3.00000e+00 (wt: 1.000e+01)
amplify-bbopt | 2024/10/04 06:09:40 | INFO | - [obj]: c=3, d=2
amplify-bbopt | 2024/10/04 06:09:40 | INFO | obj_2: 1.20000e+01 (wt: 1.000e+00)
amplify-bbopt | 2024/10/04 06:09:40 | INFO | y_hat_total=9.000e+00, y_best=0.000e+00
multi_optimizer.best_solution={'a': True, 'b': 3, 'c': 3, 'd': 1}
multi_optimizer.best_objective=0.000e+00

Setting a weight function

You can also pass your own weight function that takes the optimizer itself. This function is called right before the optimization model (amplify.Model) is made at each cycle.

Here is an example of such weight function, which uses the KernelQAOptimizer.i_cycle attribute to compute the weight. You can see that the weight (wt.) for obj_1 evolves from 1.0 to 2.0 in the execution output below.

def my_weight(optimizer: KernelQAOptimizer) -> float:
    """Compute weight based on the current number of optimization cycle."""
    weight = optimizer.i_cycle + 1.0
    return weight


optimizer_1 = KernelQAOptimizer(data_1, obj_1, objective_weight=my_weight)
optimizer_2 = KernelQAOptimizer(data_2, obj_2)

multi_optimizer = MultiObjectiveOptimizer(
    [optimizer_1, optimizer_2], client=client
)

multi_optimizer.optimize(num_cycles=2)

print(f"{multi_optimizer.best_solution=}")  # Solution (optimal input)
print(f"{multi_optimizer.best_objective=:.3e}")  # Objective function value
Hide code cell output
amplify-bbopt | 2024/10/04 06:09:40 | INFO | ----------------------------------------
amplify-bbopt | 2024/10/04 06:09:40 | INFO | #1/2 optimization cycle, constraint wt: 1.50e+02
amplify-bbopt | 2024/10/04 06:09:40 | INFO | model corrcoef: 0.985, beta: 0.0
amplify-bbopt | 2024/10/04 06:09:40 | INFO | model corrcoef: 1.000, beta: 0.0
amplify-bbopt | 2024/10/04 06:09:43 | INFO | - [obj]: a=True, b=1
amplify-bbopt | 2024/10/04 06:09:43 | INFO | obj_1: -1.00000e+00 (wt: 1.000e+00)
amplify-bbopt | 2024/10/04 06:09:43 | INFO | - [obj]: c=1, d=1
amplify-bbopt | 2024/10/04 06:09:43 | INFO | obj_2: 1.00000e+00 (wt: 1.000e+00)
amplify-bbopt | 2024/10/04 06:09:43 | INFO | y_hat_total=0.000e+00, y_best=0.000e+00
amplify-bbopt | 2024/10/04 06:09:43 | INFO | ----------------------------------------
amplify-bbopt | 2024/10/04 06:09:43 | INFO | #2/2 optimization cycle, constraint wt: 1.50e+02
amplify-bbopt | 2024/10/04 06:09:43 | INFO | model corrcoef: 0.984, beta: 0.0
amplify-bbopt | 2024/10/04 06:09:43 | INFO | model corrcoef: 1.000, beta: 0.0
amplify-bbopt | 2024/10/04 06:09:46 | INFO | identical input was obtained (0), x_hat={'a': True, 'b': 1, 'c': 1, 'd': 1}.
amplify-bbopt | 2024/10/04 06:09:46 | INFO | - [obj]: a=True, b=1
amplify-bbopt | 2024/10/04 06:09:46 | INFO | obj_1: -1.00000e+00 (wt: 2.000e+00)
amplify-bbopt | 2024/10/04 06:09:46 | INFO | - [obj]: c=1, d=2
amplify-bbopt | 2024/10/04 06:09:46 | INFO | obj_2: 4.00000e+00 (wt: 1.000e+00)
amplify-bbopt | 2024/10/04 06:09:46 | INFO | y_hat_total=3.000e+00, y_best=0.000e+00
multi_optimizer.best_solution={'a': True, 'b': 1, 'c': 1, 'd': 1}
multi_optimizer.best_objective=0.000e+00

Flexible initial data preparation

In the above Basic usage, initial training datasets for the objective functions, obj_1 and obj_2 are generated by the same DatasetGenerator instance for the same amount (num_samples=3). However, there may be cases where one objective function is more complex and requires larger dataset than the others. Or, you already have enough training dataset for one of the objective functions from the past experiments, and only need to prepare datasets for the rest of the objective functions. In such cases, you can generate training datasets for each objective function seperately.

Note

When DatasetGenerator.generate is used seperately for different objective functions, there are some limitations:

  • Constraints that uses variables across different objective functions cannot be considered. If there are such constraints, set meet_constraints=False in the instantiation of the DatasetGenerator class.

  • If there are decision variables that are used in multiple black-box objective functions, the values of the such variables in the generated training datasets are likely to take different values for different objective functions.

That said, the above limitations should not matter to the construction of individual surrogate/acquisition models during optimization cycles, as each model is constructed seperately and does not need to know the other objective functions.

Here is an exmaples of generating datasets seperately with different numbers of samples.

# Creating datasets with different numbers of samples.
data_1 = DatasetGenerator(obj_1, meet_constraints=False).generate(num_samples=3)
data_2 = DatasetGenerator(obj_2, meet_constraints=False).generate(num_samples=5)

optimizer_1 = KernelQAOptimizer(data_1, obj_1)
optimizer_2 = KernelQAOptimizer(data_2, obj_2)

multi_optimizer = MultiObjectiveOptimizer(
    [optimizer_1, optimizer_2], client=client
)

multi_optimizer.optimize(num_cycles=2)

print(f"{multi_optimizer.best_solution=}")  # Solution (optimal input)
print(f"{multi_optimizer.best_objective=:.3e}")  # Objective function value
Hide code cell output
amplify-bbopt | 2024/10/04 06:09:46 | INFO | ----------------------------------------
amplify-bbopt | 2024/10/04 06:09:46 | INFO | #0/3 initial data for obj_1
amplify-bbopt | 2024/10/04 06:09:46 | INFO | - [obj]: a=True, b=4
amplify-bbopt | 2024/10/04 06:09:46 | INFO | ----------------------------------------
amplify-bbopt | 2024/10/04 06:09:46 | INFO | #1/3 initial data for obj_1
amplify-bbopt | 2024/10/04 06:09:46 | INFO | - [obj]: a=True, b=2
amplify-bbopt | 2024/10/04 06:09:46 | INFO | ----------------------------------------
amplify-bbopt | 2024/10/04 06:09:46 | INFO | #2/3 initial data for obj_1
amplify-bbopt | 2024/10/04 06:09:46 | INFO | - [obj]: a=False, b=1
amplify-bbopt | 2024/10/04 06:09:46 | INFO | ----------------------------------------
amplify-bbopt | 2024/10/04 06:09:46 | INFO | #0/5 initial data for obj_2
amplify-bbopt | 2024/10/04 06:09:46 | INFO | - [obj]: c=5, d=4
amplify-bbopt | 2024/10/04 06:09:46 | INFO | ----------------------------------------
amplify-bbopt | 2024/10/04 06:09:46 | INFO | #1/5 initial data for obj_2
amplify-bbopt | 2024/10/04 06:09:46 | INFO | - [obj]: c=3, d=2
amplify-bbopt | 2024/10/04 06:09:46 | INFO | ----------------------------------------
amplify-bbopt | 2024/10/04 06:09:46 | INFO | #2/5 initial data for obj_2
amplify-bbopt | 2024/10/04 06:09:46 | INFO | - [obj]: c=2, d=1
amplify-bbopt | 2024/10/04 06:09:46 | INFO | ----------------------------------------
amplify-bbopt | 2024/10/04 06:09:46 | INFO | #3/5 initial data for obj_2
amplify-bbopt | 2024/10/04 06:09:46 | INFO | - [obj]: c=1, d=1
amplify-bbopt | 2024/10/04 06:09:46 | INFO | ----------------------------------------
amplify-bbopt | 2024/10/04 06:09:46 | INFO | #4/5 initial data for obj_2
amplify-bbopt | 2024/10/04 06:09:46 | INFO | - [obj]: c=1, d=5
amplify-bbopt | 2024/10/04 06:09:46 | INFO | ----------------------------------------
amplify-bbopt | 2024/10/04 06:09:46 | INFO | #1/2 optimization cycle, constraint wt: 1.60e+02
amplify-bbopt | 2024/10/04 06:09:46 | INFO | model corrcoef: 0.986, beta: 0.0
amplify-bbopt | 2024/10/04 06:09:46 | INFO | model corrcoef: 1.000, beta: 0.0
amplify-bbopt | 2024/10/04 06:09:50 | INFO | - [obj]: a=True, b=1
amplify-bbopt | 2024/10/04 06:09:50 | INFO | obj_1: -1.00000e+00 (wt: 1.000e+00)
amplify-bbopt | 2024/10/04 06:09:50 | INFO | - [obj]: c=1, d=1
amplify-bbopt | 2024/10/04 06:09:50 | INFO | obj_2: 1.00000e+00 (wt: 1.000e+00)
amplify-bbopt | 2024/10/04 06:09:50 | INFO | y_hat_total=0.000e+00, y_best=0.000e+00
amplify-bbopt | 2024/10/04 06:09:50 | INFO | ----------------------------------------
amplify-bbopt | 2024/10/04 06:09:50 | INFO | #2/2 optimization cycle, constraint wt: 1.60e+02
amplify-bbopt | 2024/10/04 06:09:50 | INFO | model corrcoef: 0.969, beta: 0.0
amplify-bbopt | 2024/10/04 06:09:50 | INFO | model corrcoef: 1.000, beta: 0.0
amplify-bbopt | 2024/10/04 06:09:53 | INFO | identical input was obtained (0), x_hat={'a': True, 'b': 1, 'c': 1, 'd': 1}.
amplify-bbopt | 2024/10/04 06:09:53 | INFO | - [obj]: a=True, b=1
amplify-bbopt | 2024/10/04 06:09:53 | INFO | obj_1: -1.00000e+00 (wt: 1.000e+00)
amplify-bbopt | 2024/10/04 06:09:53 | INFO | - [obj]: c=1, d=2
amplify-bbopt | 2024/10/04 06:09:53 | INFO | obj_2: 4.00000e+00 (wt: 1.000e+00)
amplify-bbopt | 2024/10/04 06:09:53 | INFO | y_hat_total=3.000e+00, y_best=0.000e+00
multi_optimizer.best_solution={'a': True, 'b': 1, 'c': 1, 'd': 1}
multi_optimizer.best_objective=0.000e+00

Here is an exmaples where the data for obj_1 is already generated beforehand and loaded from the saved data, while the data for obj_2 is generated by using DatasetGenerator. As noted above, do not forget to set meet_constraints=False when there is a constraint which considers variables in different black-box opbjective functions.

from amplify_bbopt import load_dataset

# Creating datasets with different numbers of samples.
data_1 = load_dataset(filepath="./data/obj_1.csv", allow_overwrite=False)
data_2 = DatasetGenerator(obj_2, meet_constraints=False).generate(num_samples=5)

optimizer_1 = KernelQAOptimizer(data_1, obj_1)
optimizer_2 = KernelQAOptimizer(data_2, obj_2)

multi_optimizer = MultiObjectiveOptimizer(
    [optimizer_1, optimizer_2], client=client
)

multi_optimizer.optimize(num_cycles=2)

print(f"{multi_optimizer.best_solution=}")  # Solution (optimal input)
print(f"{multi_optimizer.best_objective=:.3e}")  # Objective function value
Hide code cell output
amplify-bbopt | 2024/10/04 06:09:53 | INFO | data loaded from ./data/obj_1.csv
amplify-bbopt | 2024/10/04 06:09:53 | INFO | ----------------------------------------
amplify-bbopt | 2024/10/04 06:09:53 | INFO | #0/5 initial data for obj_2
amplify-bbopt | 2024/10/04 06:09:53 | INFO | - [obj]: c=5, d=4
amplify-bbopt | 2024/10/04 06:09:53 | INFO | ----------------------------------------
amplify-bbopt | 2024/10/04 06:09:53 | INFO | #1/5 initial data for obj_2
amplify-bbopt | 2024/10/04 06:09:53 | INFO | - [obj]: c=3, d=2
amplify-bbopt | 2024/10/04 06:09:53 | INFO | ----------------------------------------
amplify-bbopt | 2024/10/04 06:09:53 | INFO | #2/5 initial data for obj_2
amplify-bbopt | 2024/10/04 06:09:53 | INFO | - [obj]: c=2, d=1
amplify-bbopt | 2024/10/04 06:09:53 | INFO | ----------------------------------------
amplify-bbopt | 2024/10/04 06:09:53 | INFO | #3/5 initial data for obj_2
amplify-bbopt | 2024/10/04 06:09:53 | INFO | - [obj]: c=1, d=1
amplify-bbopt | 2024/10/04 06:09:53 | INFO | ----------------------------------------
amplify-bbopt | 2024/10/04 06:09:53 | INFO | #4/5 initial data for obj_2
amplify-bbopt | 2024/10/04 06:09:53 | INFO | - [obj]: c=1, d=5
amplify-bbopt | 2024/10/04 06:09:53 | INFO | ----------------------------------------
amplify-bbopt | 2024/10/04 06:09:53 | INFO | #1/2 optimization cycle, constraint wt: 1.60e+02
amplify-bbopt | 2024/10/04 06:09:53 | INFO | model corrcoef: 0.967, beta: 0.0
amplify-bbopt | 2024/10/04 06:09:53 | INFO | model corrcoef: 1.000, beta: 0.0
amplify-bbopt | 2024/10/04 06:09:56 | INFO | - [obj]: a=True, b=1
amplify-bbopt | 2024/10/04 06:09:56 | INFO | obj_1: -1.00000e+00 (wt: 1.000e+00)
amplify-bbopt | 2024/10/04 06:09:56 | INFO | - [obj]: c=1, d=1
amplify-bbopt | 2024/10/04 06:09:56 | INFO | obj_2: 1.00000e+00 (wt: 1.000e+00)
amplify-bbopt | 2024/10/04 06:09:56 | INFO | y_hat_total=0.000e+00, y_best=0.000e+00
amplify-bbopt | 2024/10/04 06:09:56 | INFO | ----------------------------------------
amplify-bbopt | 2024/10/04 06:09:56 | INFO | #2/2 optimization cycle, constraint wt: 1.60e+02
amplify-bbopt | 2024/10/04 06:09:56 | INFO | model corrcoef: 0.967, beta: 0.0
amplify-bbopt | 2024/10/04 06:09:56 | INFO | model corrcoef: 1.000, beta: 0.0
amplify-bbopt | 2024/10/04 06:09:59 | INFO | identical input was obtained (0), x_hat={'a': True, 'b': 1, 'c': 1, 'd': 1}.
amplify-bbopt | 2024/10/04 06:09:59 | INFO | - [obj]: a=True, b=1
amplify-bbopt | 2024/10/04 06:09:59 | INFO | obj_1: -1.00000e+00 (wt: 1.000e+00)
amplify-bbopt | 2024/10/04 06:09:59 | INFO | - [obj]: c=1, d=2
amplify-bbopt | 2024/10/04 06:09:59 | INFO | obj_2: 4.00000e+00 (wt: 1.000e+00)
amplify-bbopt | 2024/10/04 06:09:59 | INFO | y_hat_total=3.000e+00, y_best=0.000e+00
multi_optimizer.best_solution={'a': True, 'b': 1, 'c': 1, 'd': 1}
multi_optimizer.best_objective=0.000e+00