Optimisation with changing environmental conditions#

Sometimes we cannot control every input parameter ourselves and some uncontrollable variables are given externally by environmental conditions. Some common examples are temperature and humidity in a lab. This example assumes that the second input is uncontrollable and aims to solve the problem conditionally on measurements (in this case simulated by adding a small positive or negative value to the value of the uncontrollable input from the previous iteration). The Hartmann6D synthetic test function acts as a substitute for a black-box objective function, such as an experiment or a simulation. The resulting data points can be used as training data of a machine learning model, such as a Gaussian process, to predict the optimal values of all controllable parameters conditionally on measurements of the uncontrollable variable. The ENVBOPredictionModel provides an implementation of this approach for easy use. The model can only be used to predict controllable parameters based on environmental values that ENVBO explored during optimisation. Going beyond the explored range will result in extrapolation that is not informative when using Gaussian processes. For example, if the range of the environmental values explored during optimisaion 0.1 to 0.4, the model should not used to predict for environmental values larger than 0.4.

The example below assumes that input 2 of the Hartmann function cannot be controlled and optimises conditionally on its values. The resulting candidates are then used to train a Gaussian process as a prediction model that predicts the optimal inputs for \(x_2 = 0.3\) with a predicted output of -3.09.

For more details, we refer to M Diessner, KJ Wilson, and RD Whalley, “On the development of a practical Bayesian optimisation algorithm for expensive experiments and simulations with changing environmental conditions,” arXiv preprint arXiv:2402.03006, 2024.

import torch
from nubo.algorithms import envbo, ENVBOPredictionModel
from nubo.test_functions import Hartmann6D
from nubo.utils import gen_inputs

# test function
func = Hartmann6D(minimise=False)
dims = 6

# specify bounds
bounds = torch.tensor([[0., 0., 0., 0., 0., 0.], [1., 1., 1., 1., 1., 1.]])

# training data
x_train = gen_inputs(num_points=dims*5,
y_train = func(x_train)

# Bayesian optimisation loop
iters = 40

for iter in range(iters):

    env_dims = 1
    env_value = x_train[-1, env_dims] + torch.normal(0.0, torch.tensor(0.05))
    env_value = torch.clamp(env_value, min=0, max=1)

    # NUBO
    x_new = envbo(x_train=x_train,

    # evaluate new point
    y_new = func(x_new)

    # add to data
    x_train = torch.vstack((x_train, x_new))
    y_train = torch.hstack((y_train, y_new))

    # print new best
    if y_new > torch.max(y_train[:-1]):
        print(f"New best at evaluation {len(y_train)}: \t Inputs: {x_new.numpy().reshape(dims).round(4)}, \t Outputs: {-y_new.numpy().round(4)}")

# results
best_iter = int(torch.argmax(y_train))
print(f"Evaluation: {best_iter+1} \t Solution: {-float(y_train[best_iter]):.4f}")

# predict optimal values of controllable parameters based on environmental measurements
model = ENVBOPredictionModel(x_train, y_train, env_dims, bounds)

x_pred, y_pred = model.predict(0.3)

print(f"Predicted optimal inputs: {x_pred}")
print(f"Predicted output: {-y_pred}")
New best at evaluation 54:       Inputs: [0.2106 0.4684 0.5974 0.2002 0.2793 0.6266],    Outputs: [-2.1139]
New best at evaluation 56:       Inputs: [0.2048 0.4332 0.5424 0.221  0.3003 0.642 ],    Outputs: [-2.4382]
New best at evaluation 57:       Inputs: [0.1765 0.4425 0.4714 0.2499 0.3253 0.6641],    Outputs: [-2.4523]
New best at evaluation 58:       Inputs: [0.1846 0.3954 0.4932 0.2447 0.3144 0.6652],    Outputs: [-2.6823]
New best at evaluation 60:       Inputs: [0.1745 0.2582 0.4871 0.2476 0.3043 0.6805],    Outputs: [-3.1487]
Evaluation: 60   Solution: -3.1487
Predicted optimal inputs: tensor([[0.1843, 0.3000, 0.4632, 0.2626, 0.3019, 0.6726]])
Predicted output: tensor([-3.0859])