This notebook may be found here.

The blackbox function Interface#

Prior to any MOO, the blackbox function needs to be implemented in Paref’s BlackboxFunction interface. This always includes the following methods and properties:

  • __call__: The blackbox function f relation of design (x) and target (f(x))

  • dimension_design_space property: The dimension of the design space

  • dimension_target_space property: The dimension of the target space

  • design_space property: The bounds of the design space (lowerbounds, upperbounds) as instance of the Bounds class

Everything else, from storing each evaluation and calculating the Pareto front to saving and loading the evaluations, is handled by the Blackbox function interface.

[ ]:
import numpy as np
from paref.interfaces.moo_algorithms.blackbox_function import BlackboxFunction
from paref.blackbox_functions.design_space.bounds import Bounds

class TestBlackboxFunction(BlackboxFunction):  # Implement the blackbox function interface
    def __call__(self, x) -> np.ndarray:
        return np.array([x[0],x[0] ** 2 + x[1]])  # The blackbox function f relation of design (x) and target (f(x))

    @property
    def dimension_design_space(self) -> int:  # The dimension of the design space
        return 2

    @property
    def dimension_target_space(self) -> int:  # The dimension of the target space
        return 2

    @property
    def design_space(
            self) -> Bounds:  # The bounds of the design space (lower bounds, upper bounds) as instance of the Bounds class
        return Bounds(lower_bounds=-1 * np.ones(2), upper_bounds=np.ones(2))


bbf = TestBlackboxFunction()  # Initialize the blackbox function

In our case, we can calculate the true Pareto front and we make use of that knowledge to visualize (and validate) our results:

[ ]:
import matplotlib.pyplot as plt
[bbf(np.array([i,-1])) for i in np.linspace(-1,0,100)]
real_pf = bbf.y
bbf.clear_evaluations()

Exploring the target space#

An initial exploration of the target space is almost always required prior to any MOO algorithm. The BlackboxFunction interface includes a (in some sense) optimal initial sampling: the Latin Hypercube Sampling (LHC).

[ ]:
bbf.perform_lhc(n=20)  # Perform a latin hypercube sampling (i.e. exploration) of the target space with n=20 samples
[ ]:
plt.plot(real_pf.T[0], real_pf.T[1], '--', label='real pareto front')
plt.scatter(bbf.y.T[0], bbf.y.T[1], label='LHC samples')
plt.legend()

The Pareto front of the evaluations (20 samples obtained by the LHC) can be accessed via the blackbox function’s pareto_front property:

[ ]:
bbf.pareto_front

Paref’s Info class#

It is often helpful to have a look at (an estimate of) the Pareto front prior to any MOO algorithm in order to guide the search process. We are interested in the shape and dimension of the Pareto front, the minima in components and if the target space is explored sufficiently. Those information (and more) are provided by the Info class.

[ ]:
from paref.express.info import Info

info = Info(blackbox_function=bbf)
[ ]:
info.suggestion_pareto_points

Paref Express#

Paref provides a low level MOO class including the most frequently used MOO algorithms - Paref Express. For example, it provides a good initial algorithm for further more tailored MOO: The minimal_search algorithm. This MOO targets Pareto points which have the property of being 1. minimal in some component and 2. a best ‘real’ trade-off between the objectives.

[ ]:
from paref.express.express_search import ExpressSearch

moo = ExpressSearch(blackbox_function=bbf)  # Create an instance of the Paref Express class
moo.minimal_search(max_evaluations=3)  # Perform the minimal search of the target space

We access the so found Pareto points via the edge_points and max_point property:

[ ]:
print("Pareto points minimal in some component: \n", moo.edge_points,
      "\n Best real trade-off: \n", moo.max_point)
[ ]:
plt.plot(real_pf.T[0], real_pf.T[1], '--', label='real pareto front')
plt.scatter(bbf.y.T[0], bbf.y.T[1], label='LHC samples')
plt.scatter(moo.edge_points.T[0], moo.edge_points.T[1], label='edge points')
plt.scatter(moo.max_point.T[0], moo.max_point.T[1], label='best real trade-off')
plt.legend()

From here, you can continue with more tailored MOO algorithms. Have a look at a full use-case of Paref or check out the other MOO algorithms.