Branin Function Example

The Branin function is a 2-dimensional continuous benchmark function commonly used to test optimization algorithms. It has three global minima and multiple local minima, making it ideal for demonstrating iterative optimization workflows like AMLRO.

Function Domain

Variable

Bounds

x₁

[-5, 10]

x₂

[0, 15]

Global Minima

  • (-π, 12.275)

  • (π, 2.275)

  • (9.42478, 2.475)

All minima have a function value of approximately 0.3979. since we going to use coaser grid Minima ~0.40

Purpose

  • Demonstrate AMLRO workflow with a computational benchmark.

  • Show reaction space generation, training set creation, and active learning prediction.

Click the badge below to open this notebook in Google Colab:

Open in Colab

from amlro.optimizer import *
from amlro.generate_reaction_conditions import *
from amlro.generate_training_data import *
import random
import matplotlib.pyplot as plt
import pandas as pd
config = {
            "continuous": {
                "bounds": [[-5, 10], [0, 15]], #
                "resolutions": [0.1, 0.1],
                "feature_names": ["f1", "f2"],
            },
            "categorical": {
                "feature_names": [],
                "values": []
            },
            "objectives": ['fx'],
            "directions": ['min'],

        }
exp_dir = 'Branin_test'
sampling = 'sobol'
training_size = 20
regresor_model = 'gb'

Step 1: Generate Reaction Space

Reaction space generation serves two purposes:

  1. Construct the full combinatorial reaction space based on user-defined continuous and categorical parameters.

  2. Select an initial subset of reactions for training using a chosen sampling strategy.

Please run the following cell/code line.

get_reaction_scope(config=config, sampling=sampling, training_size=training_size, write_files=True, exp_dir=exp_dir)
d:\amlro\venv\lib\site-packages\scipy\stats\_qmc.py:763: UserWarning: The balance properties of Sobol' points require n to be a power of 2.
  sample = self._random(n, workers=workers)
(         f1    f2
 0      -5.0   0.0
 1      -5.0   0.1
 2      -5.0   0.2
 3      -5.0   0.3
 4      -5.0   0.4
 ...     ...   ...
 22796  10.0  14.6
 22797  10.0  14.7
 22798  10.0  14.8
 22799  10.0  14.9
 22800  10.0  15.0
 
 [22801 rows x 2 columns],
          f1    f2
 0      -5.0   0.0
 1      -5.0   0.1
 2      -5.0   0.2
 3      -5.0   0.3
 4      -5.0   0.4
 ...     ...   ...
 22796  10.0  14.6
 22797  10.0  14.7
 22798  10.0  14.8
 22799  10.0  14.9
 22800  10.0  15.0
 
 [22801 rows x 2 columns],
      f1    f2
 0  -5.0   0.0
 1   2.5   7.5
 2   6.3   3.7
 3  -1.2  11.2
 4   0.6   5.6
 5   8.1  13.1
 6   4.4   1.9
 7  -3.1   9.4
 8  -2.2   4.7
 9   5.3  12.2
 10  9.1   0.9
 11  1.6   8.4
 12 -0.3   2.8
 13  7.2  10.3
 14  3.4   6.6
 15 -4.1  14.1
 16 -3.6   7.0
 17  3.9  14.5
 18  7.7   3.3
 19  0.2  10.8)

Define Branin objective function

The Branin function is defined as:

[ f(x_1, x_2) = a (x_2 - b x_1^2 + c x_1 - r)^2 + s (1 - t) \cos(x_1) + s ]

with typical constants:

  • (a = 1)

  • (b = 5.1 / (4 \pi^2))

  • (c = 5 / \pi)

  • (r = 6)

  • (s = 10)

  • (t = 1 / (8 \pi))

def branin(x):
    
    x1, x2 = np.array(x)

    a = 1.0
    b = 5.1 / (4.0 * np.pi**2)
    c = 5.0 / np.pi
    r = 6.0
    s = 10.0
    t = 1.0 / (8.0 * np.pi)
    
    
    return a * (x2 - b * x1**2 + c * x1 - r)**2 + s * (1 - t) * np.cos(x1) + s

Step 2: Initial Training Data (closed-loop)

This step collects objective results for the initial parameter combinations.

  • objective values are collected by calling branin objective function each iteration.

parameters = []
objectives = []

for i in range(training_size):

    parameters = generate_training_data(exp_dir=exp_dir,config=config,parameters=parameters, obj_values=objectives)
    print(parameters)
    objectives = [branin(parameters)] # Branin objective function

generate_training_data(exp_dir=exp_dir,config=config,parameters=parameters, obj_values=objectives,termination=True)
[-5.0, 0.0]
writing data to training dataset files...
[2.5, 7.5]
writing data to training dataset files...
[6.3, 3.7]
writing data to training dataset files...
[-1.2, 11.2]
writing data to training dataset files...
[0.6, 5.6]
writing data to training dataset files...
[8.1, 13.1]
writing data to training dataset files...
[4.4, 1.9]
writing data to training dataset files...
[-3.1, 9.4]
writing data to training dataset files...
[-2.2, 4.7]
writing data to training dataset files...
[5.3, 12.2]
writing data to training dataset files...
[9.1, 0.9]
writing data to training dataset files...
[1.6, 8.4]
writing data to training dataset files...
[-0.3, 2.8]
writing data to training dataset files...
[7.2, 10.3]
writing data to training dataset files...
[3.4, 6.6]
writing data to training dataset files...
[-4.1, 14.1]
writing data to training dataset files...
[-3.6, 7.0]
writing data to training dataset files...
[3.9, 14.5]
writing data to training dataset files...
[7.7, 3.3]
writing data to training dataset files...
[0.2, 10.8]
writing data to training dataset files...
Training set generation completed..

Step 3: Active Learning Optimization Loop

This step uses the initial dataset to train a model and predict the next batch of parameters for minimizing the objective function

%%capture
parameters = []
objectives = []
for i in range(1):
    parameters = get_optimized_parameters(exp_dir=exp_dir,config=config, parameters_list=parameters,objectives_list=objectives, batch_size=1, model= regresor_model)
    print(parameters)
    #objectives = [[branin(parameters[0])],[branin(parameters[1])]] #if you use batch size = 2 You need to call branin fucntion two time for each parameter set.
    objectives = [[branin(parameters[0])]] #user input

get_optimized_parameters(exp_dir=exp_dir,config=config, parameters_list=parameters,objectives_list=objectives, batch_size=1, termination=True)
df = pd.read_csv(f'{exp_dir}/reactions_data.csv')
plt.plot(df['fx'],marker='o', label="F(x) Branin")
plt.axvspan(0, training_size, color='yellow', alpha=0.3)
min_idx = df['fx'].idxmin()
min_val = df['fx'].min()
plt.scatter(min_idx, min_val, color='red', s=5, zorder=5)
plt.annotate(
    f"Min = {min_val:.2f}\nIter = {min_idx}",
    xy=(min_idx, min_val),
    xytext=(min_idx + 1.5, min_val + 1.2),
    arrowprops=dict(facecolor='black', arrowstyle='->'),
)
plt.legend(loc='upper left')
plt.xlabel('Optimization steps')
plt.ylabel('F(x) Branin')
plt.ylim(0.30,2)
plt.title('Trajectories of Objectives')
Text(0.5, 1.0, 'Trajectories of Objectives')
../_images/aa5437ec425b5208d12858c5d691a6b64bad48502ea9ce7b8fb4e260eece8768.png
x1 = np.linspace(-5, 10, 100)
x2 = np.linspace(0, 15, 100)
X1, X2 = np.meshgrid(x1, x2)
Z = branin([X1, X2])
plt.figure(figsize=(10, 8))
plt.contour(X1, X2, Z, levels=50, cmap='magma_r')
plt.colorbar(label='Fx Branin')

plt.plot(df['f1'][training_size:], df['f2'][training_size:], 'o-', color='red', markersize=6, label='Optimization Path')
plt.plot(df['f1'][:training_size], df['f2'][:training_size], 'o', color='blue', markersize=6, label='training Points')

min_idx = df['fx'].idxmin()
min_val = df['fx'].min()
min_f1 = df.loc[min_idx, 'f1']
min_f2 = df.loc[min_idx, 'f2']

# Highlight minimum point
plt.scatter(min_f1, min_f2, color='green', s=10, zorder=5, label='Minimum fx')

# Annotate with value and iteration
plt.annotate(
    f"Min fx = {min_val:.2f}\nIter = {min_idx} \n [f1 {min_f1}, f2 {min_f2}]",
    xy=(min_f1, min_f2),
    xytext=(min_f1 - 2.2, min_f2 + 2.2),  # offset for visibility
    arrowprops=dict(facecolor='black', arrowstyle='->'),
)

plt.xlabel('F1')
plt.ylabel('F2')

plt.legend()
plt.title('Optimization path')
Text(0.5, 1.0, 'Optimization path')
../_images/567055f9edfd2d6ad10502e89c73c81c360da9ea5d773d31d1a82f5f4dbe4efb.png