Custom Planner

In this example, you will learn how to integrate your own optimization algorithm to be used within Olympus. As a simple example, here we will create a Planner that implements a random sampler. To better understand what is happening in the code and how to further customize your Planner, we suggest you take a look at the “Developer’s Guide” section of the documentation.

First, we import the CustomPlanner class.

[1]:
from olympus.planners import CustomPlanner

Then we define a class that inherits from CustomPlanner and implements the _ask method.

[2]:
import numpy as np
from olympus import ParameterVector

class RandomSampler(CustomPlanner):

    def _ask(self):
        new_params = []
        for param in self._param_space:
            new_param = np.random.uniform(low=param['domain'][0], high=param['domain'][1])
            new_params.append(new_param)

        return ParameterVector(array=new_params, param_space=self.param_space)

Let’s check whether we can not use our new algorithm within Olympus.

[3]:
from olympus import Surface

# initialise an analytical toy surface
surface = Surface(kind='Dejong', param_dim=2)

# initialise our planner
planner = RandomSampler()
[4]:
# optimise the surface for 10 iterations
campaign = planner.optimize(surface, num_iter=10, verbose=True)
[INFO] Optimize iteration 1
[INFO] Obtaining parameters from planner...
[INFO] Obtaining measurement from emulator...
[INFO] Optimize iteration 2
[INFO] Obtaining parameters from planner...
[INFO] Obtaining measurement from emulator...
[INFO] Optimize iteration 3
[INFO] Obtaining parameters from planner...
[INFO] Obtaining measurement from emulator...
[INFO] Optimize iteration 4
[INFO] Obtaining parameters from planner...
[INFO] Obtaining measurement from emulator...
[INFO] Optimize iteration 5
[INFO] Obtaining parameters from planner...
[INFO] Obtaining measurement from emulator...
[INFO] Optimize iteration 6
[INFO] Obtaining parameters from planner...
[INFO] Obtaining measurement from emulator...
[INFO] Optimize iteration 7
[INFO] Obtaining parameters from planner...
[INFO] Obtaining measurement from emulator...
[INFO] Optimize iteration 8
[INFO] Obtaining parameters from planner...
[INFO] Obtaining measurement from emulator...
[INFO] Optimize iteration 9
[INFO] Obtaining parameters from planner...
[INFO] Obtaining measurement from emulator...
[INFO] Optimize iteration 10
[INFO] Obtaining parameters from planner...
[INFO] Obtaining measurement from emulator...

[5]:
# show the parameter values we queried and their merit
for p, v in zip(campaign.observations.get_params(), campaign.observations.get_values()):
    print(p, v)
[0.87168557 0.62387334] [3.04089887]
[0.61641219 0.14017234] [2.9758572]
[0.10831711 0.32985981] [3.28347596]
[0.53705365 0.01023886] [2.82177185]
[0.08795505 0.38948555] [3.08114755]
[0.23275568 0.63885554] [2.8131307]
[0.3896672  0.70026978] [2.46556129]
[0.44465784 0.9749147 ] [2.92317688]
[0.1069464  0.32465186] [3.30674879]
[0.64876744 0.03969659] [3.36517084]

Now we realised that our custom planner is stochastic and does not allow for reproducible results, so we would like to add a random_seed argument such that we fix the random seed if desired. To do so, we overwrite the __init__ method inherited from CustomPlanner and add random_seed as an argument.

[6]:
from olympus import ParameterVector
from olympus.planners import CustomPlanner, AbstractPlanner
import numpy as np

class RandomSampler(CustomPlanner):
    """My custom sampler"""

    def __init__(self, goal='minimize', random_seed=None):
        AbstractPlanner.__init__(**locals())
        np.random.seed(self.random_seed)

    def _ask(self):
        new_params = []
        for param in self._param_space:
            new_param = np.random.uniform(low=param['domain'][0], high=param['domain'][1])
            new_params.append(new_param)

        return ParameterVector(array=new_params, param_space=self.param_space)
[7]:
# do the optimization twice
for i in range(2):
    surface = Surface(kind='Dejong', param_dim=2)
    planner = RandomSampler(random_seed=42)
    campaign = planner.optimize(surface, num_iter=3, verbose=False)

    print(f'Optimization Number {i+1}')
    for p, v in zip(campaign.observations.get_params(), campaign.observations.get_values()):
        print(p, v)

    print()
Optimization Number 1
[0.37454012 0.95071431] [3.24309206]
[0.73199394 0.59865848] [2.51640451]
[0.15601864 0.15599452] [3.70941192]

Optimization Number 2
[0.37454012 0.95071431] [3.24309206]
[0.73199394 0.59865848] [2.51640451]
[0.15601864 0.15599452] [3.70941192]

As you can/should see above, the first and second optimizations queried the same points as we have now fixed the random seed.

[ ]: