Multiobjective optimization with Pareto front mapping¶
One way to do multiobjective optimization is with Pareto optimization, which explores the set of Pareto-efficient points. A point is Pareto-efficient if there are no other valid points that are better at every objective: it shows the “trade-off” between several objectives.
[1]:
from blop.utils import prepare_re_env # noqa
%run -i $prepare_re_env.__file__ --db-type=temp
[2]:
import numpy as np
from blop import DOF, Agent, Objective
def digestion(df):
for index, entry in df.iterrows():
x1, x2 = entry.x1, entry.x2
df.loc[index, "f1"] = (x1 - 2) ** 2 + (x2 - 1) + 2
df.loc[index, "f2"] = 9 * x1 - (x2 - 1) + 2
df.loc[index, "c1"] = x1**2 + x2**2
df.loc[index, "c2"] = x1 - 3 * x2 + 10
return df
dofs = [
DOF(name="x1", search_domain=(-20, 20)),
DOF(name="x2", search_domain=(-20, 20)),
]
objectives = [
Objective(name="f1", target="min"),
Objective(name="f2", target="min"),
Objective(name="c1", constraint=(-np.inf, 225)),
Objective(name="c2", constraint=(-np.inf, 0)),
]
agent = Agent(
dofs=dofs,
objectives=objectives,
digestion=digestion,
db=db,
)
(uid,) = RE(agent.learn("qr", n=64))
2025-06-19 02:04:23.536 INFO: Executing plan <generator object Agent.learn at 0x7f6c8419d150>
2025-06-19 02:04:23.538 INFO: Change state on <bluesky.run_engine.RunEngine object at 0x7f6b6adfa8c0> from 'idle' -> 'running'
Transient Scan ID: 1 Time: 2025-06-19 02:04:24
Persistent Unique Scan ID: 'a5eb457d-c414-47d1-b408-9cbff60f76fc'
New stream: 'primary'
+-----------+------------+------------+------------+
| seq_num | time | x1 | x2 |
+-----------+------------+------------+------------+
| 1 | 02:04:24.6 | -1.687 | 1.074 |
| 2 | 02:04:24.6 | -5.687 | 3.469 |
| 3 | 02:04:24.6 | -10.085 | 1.692 |
| 4 | 02:04:24.6 | -10.839 | -1.526 |
| 5 | 02:04:24.6 | -16.866 | -3.916 |
| 6 | 02:04:24.6 | -14.868 | -5.780 |
| 7 | 02:04:24.6 | -17.740 | -8.150 |
| 8 | 02:04:24.6 | -15.841 | -11.215 |
| 9 | 02:04:24.6 | -19.859 | -16.718 |
| 10 | 02:04:24.6 | -12.767 | -19.088 |
| 11 | 02:04:24.6 | -11.845 | -13.605 |
| 12 | 02:04:24.6 | -7.364 | -11.868 |
| 13 | 02:04:24.6 | -8.335 | -16.063 |
| 14 | 02:04:24.6 | -4.369 | -18.453 |
| 15 | 02:04:24.7 | -0.244 | -14.239 |
| 16 | 02:04:24.7 | 0.241 | -10.275 |
| 17 | 02:04:24.7 | -9.342 | -8.766 |
| 18 | 02:04:24.7 | -5.264 | -3.303 |
| 19 | 02:04:24.7 | -2.363 | -0.933 |
| 20 | 02:04:24.7 | -3.344 | -6.376 |
| 21 | 02:04:24.7 | 2.361 | -4.856 |
| 22 | 02:04:24.7 | 3.342 | -7.840 |
| 23 | 02:04:24.7 | 7.366 | -12.670 |
| 24 | 02:04:24.7 | 4.368 | -17.028 |
| 25 | 02:04:24.7 | 8.339 | -19.403 |
| 26 | 02:04:24.7 | 11.842 | -12.178 |
| 27 | 02:04:24.7 | 12.766 | -15.123 |
| 28 | 02:04:24.7 | 19.862 | -17.518 |
| 29 | 02:04:24.7 | 15.842 | -14.554 |
| 30 | 02:04:24.7 | 14.865 | -9.706 |
| 31 | 02:04:24.7 | 17.742 | -7.311 |
| 32 | 02:04:24.7 | 9.344 | -5.465 |
| 33 | 02:04:24.8 | 5.267 | -2.461 |
| 34 | 02:04:24.8 | 10.837 | -2.993 |
| 35 | 02:04:24.8 | 16.868 | -0.618 |
| 36 | 02:04:24.8 | 17.207 | 0.139 |
| 37 | 02:04:24.8 | 18.180 | 7.166 |
| 38 | 02:04:24.8 | 14.214 | 9.537 |
| 39 | 02:04:24.8 | 15.108 | 14.719 |
| 40 | 02:04:24.8 | 19.185 | 17.975 |
| 41 | 02:04:24.8 | 13.188 | 15.604 |
| 42 | 02:04:24.8 | 12.207 | 12.329 |
| 43 | 02:04:24.8 | 6.710 | 12.813 |
| 44 | 02:04:24.8 | 7.584 | 19.879 |
| 45 | 02:04:24.8 | 4.711 | 17.489 |
| 46 | 02:04:24.8 | -0.680 | 13.784 |
| 47 | 02:04:24.8 | -4.709 | 18.290 |
| 48 | 02:04:24.9 | -7.586 | 15.915 |
| 49 | 02:04:24.9 | -13.185 | 18.944 |
+-----------+------------+------------+------------+
| seq_num | time | x1 | x2 |
+-----------+------------+------------+------------+
| 50 | 02:04:24.9 | -19.188 | 16.549 |
| 51 | 02:04:24.9 | -15.110 | 10.753 |
| 52 | 02:04:24.9 | -18.181 | 8.632 |
| 53 | 02:04:24.9 | -17.210 | 4.067 |
| 54 | 02:04:24.9 | -14.210 | 6.237 |
| 55 | 02:04:24.9 | -9.705 | 9.227 |
| 56 | 02:04:24.9 | -12.204 | 13.128 |
| 57 | 02:04:24.9 | -6.712 | 11.389 |
| 58 | 02:04:24.9 | -2.609 | 6.851 |
| 59 | 02:04:24.9 | 0.683 | 10.442 |
| 60 | 02:04:24.9 | 2.612 | 7.692 |
| 61 | 02:04:24.9 | 1.688 | 4.377 |
| 62 | 02:04:24.9 | 5.684 | 2.007 |
| 63 | 02:04:25.0 | 10.087 | 2.529 |
| 64 | 02:04:25.0 | 9.703 | 5.302 |
+-----------+------------+------------+------------+
generator list_scan ['a5eb457d'] (scan num: 1)
2025-06-19 02:04:26.894 INFO: Change state on <bluesky.run_engine.RunEngine object at 0x7f6b6adfa8c0> from 'running' -> 'idle'
2025-06-19 02:04:26.896 INFO: Cleaned up from plan <generator object Agent.learn at 0x7f6c8419d150>
We can plot our fitness and constraint objectives to see their models:
[3]:
agent.plot_objectives()

We can plot the Pareto front (the set of all Pareto-efficient points), which shows the trade-off between the two fitnesses. The points in blue comprise the Pareto front, while the points in red are either not Pareto efficient or are invalidated by one of the constraints.
[4]:
agent.plot_pareto_front()

We can explore the Pareto front by choosing a random point on the Pareto front and computing the expected improvement in the hypervolume of all fitness objectives with respect to that point (called the “reference point”). All this is done automatically with the qnehvi
acquisition function:
[5]:
# this is broken now but is fixed in the next PR
# RE(agent.learn("qnehvi", n=4))