libEnsemble Tutorial

The following libe_basic_ex.py code is an example of basic ParMOO + libEnsemble usage from the Extras and Plugins section of the User Guide.


import numpy as np
from parmoo.extras.libe import libE_MOOP
from parmoo.searches import LatinHypercube
from parmoo.surrogates import GaussRBF
from parmoo.acquisitions import UniformWeights
from parmoo.optimizers import LocalGPS

# When running with MPI, we need to keep track of which thread is the manager
# using libensemble.tools.parse_args()
from libensemble.tools import parse_args
_, is_manager, _, _ = parse_args()

# All functions are defined below.

def sim_func(x):
   if x["x2"] == 0:
      return np.array([(x["x1"] - 0.2) ** 2, (x["x1"] - 0.8) ** 2])
   else:
      return np.array([99.9, 99.9])

def obj_f1(x, s):
    return s["MySim"][0]

def obj_f2(x, s):
    return s["MySim"][1]

def const_c1(x, s):
    return 0.1 - x["x1"]

# When using libEnsemble with Python MP, the "solve" command must be enclosed
# in an "if __name__ == '__main__':" block, as shown below
if __name__ == "__main__":
    # Fix the random seed for reproducibility
    np.random.seed(0)

    # Create a libE_MOOP
    my_moop = libE_MOOP(LocalGPS)
    
    # Add 2 design variables (one continuous and one categorical)
    my_moop.addDesign({'name': "x1",
                       'des_type': "continuous",
                       'lb': 0.0, 'ub': 1.0})
    my_moop.addDesign({'name': "x2", 'des_type': "categorical",
                       'levels': 3})
    
    # Add the simulation (note the budget of 20 sim evals during search phase)
    my_moop.addSimulation({'name': "MySim",
                           'm': 2,
                           'sim_func': sim_func,
                           'search': LatinHypercube,
                           'surrogate': GaussRBF,
                           'hyperparams': {'search_budget': 20}})
    
    # Add the objectives
    my_moop.addObjective({'name': "f1", 'obj_func': obj_f1})
    my_moop.addObjective({'name': "f2", 'obj_func': obj_f2})
    
    # Add the constraint
    my_moop.addConstraint({'name': "c1", 'constraint': const_c1})
    
    # Add 3 acquisition functions
    for i in range(3):
       my_moop.addAcquisition({'acquisition': UniformWeights,
                               'hyperparams': {}})
    
    # Turn on checkpointing -- creates files parmoo.moop & parmoo.surrogate.1
    my_moop.setCheckpoint(True, checkpoint_data=False, filename="parmoo")
    
    # Use sim_max = 30 to perform just 30 simulations
    my_moop.solve(sim_max=30)
    
    # Display the solution -- this "if" clause is needed when running with MPI
    if is_manager:
        results = my_moop.getPF(format="pandas")
        print(results)

You can run the above script with MPI

mpirun -np N python3 libe_basic_ex.py

or with Python’s built-in multiprocessing module.

python3 libe_basic_ex.py --comms local --nworkers N

The resulting output is shown below.

         x1  x2        f1        f2        c1
0  0.742825   0  0.294659  0.003269 -0.642825
1  0.680283   0  0.230672  0.014332 -0.580283
2  0.616501   0  0.173473  0.033672 -0.516501
3  0.580369   0  0.144680  0.048238 -0.480369
4  0.555222   0  0.126183  0.059916 -0.455222
5  0.518980   0  0.101749  0.078972 -0.418980
6  0.475477   0  0.075888  0.105315 -0.375477
7  0.302503   0  0.010507  0.247503 -0.202503
8  0.201285   0  0.000002  0.358460 -0.101285