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 GlobalSurrogate_PS

# 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):
   sx = np.array([(x["x1"] - 0.2) ** 2, (x["x1"] - 0.8) ** 2])
   return 99. - 99. * (x["x2"] == 0) + sx

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__":
    # Create a libE_MOOP -- fix the random seed for reproducibility
    my_moop = libE_MOOP(GlobalSurrogate_PS, hyperparams={'np_random_gen': 0})
    
    # 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 simulation evaluations
    # 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, 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+1}

The resulting output is shown below.

          x1  x2        f1        f2        c1
0   0.827822   0  0.394161  0.000774 -0.727822
1   0.710334   0  0.260440  0.008040 -0.610334
2   0.695881   0  0.245897  0.010841 -0.595881
3   0.645392   0  0.198374  0.023904 -0.545392
4   0.629416   0  0.184398  0.029099 -0.529416
5   0.624045   0  0.179814  0.030960 -0.524045
6   0.554025   0  0.125334  0.060504 -0.454025
7   0.552853   0  0.124505  0.061082 -0.452853
8   0.441567   0  0.058354  0.128475 -0.341567
9   0.429848   0  0.052830  0.137013 -0.329848
10  0.281606   0  0.006659  0.268733 -0.181606
11  0.261684   0  0.003805  0.289784 -0.161684
12  0.252531   0  0.002760  0.299722 -0.152531
13  0.249211   0  0.002422  0.303369 -0.149211