Using Evolutionary Algorithms in DESDEO (Functional)¶
Here, we will show multiple ways of using Evolutionary Algorithms (EAs) in DESDEO, depending on the user's needs. Be sure to read the explanation on how these algorithms are structured. We will showcase three different ways of using EAs in DESDEO:
- Using inbuilt algorithms in DESDEO for to calculate a representative set of solutions for multi-objective optimization problems.
- Using inbuilt algorithms in DESDEO for to interactive evolutionary multi-objective optimization.
- Using components implemented in DESDEO to build custom EAs (interactive or not).
Using inbuilt algorithms in DESDEO¶
First, let's import the necessary classes and functions from DESDEO.
import warnings
import plotly.graph_objects as go
from desdeo.emo import algorithms, preference_handling
from desdeo.problem.testproblems import dtlz2
# Suppress UserWarnings from not having all external solvers installed.
# This is not a problem, as we are not using any external solvers in this example.
warnings.filterwarnings("ignore", category=UserWarning)
First, we instantiate the problem we want to solve. We will use the DTLZ2 problem from the desdeo.problem.testproblems module.
Then, we build a solver for it with algorithms.emo_constructor, passing the default NSGA-III options from algorithms.nsga3_options(). The constructor returns a solver and an extras object. The solver can be run to solve the problem using NSGA-III, while the extras object holds additional components, such as an archive of solutions. We will see the usage of extras further down.
problem = dtlz2(n_variables=10, n_objectives=3)
solver, extras = algorithms.emo_constructor(emo_options=algorithms.nsga3_options(), problem=problem)
result = solver()
Running the solver object will return a EMOResult object, which contains the final population of solutions.
print(result.optimal_variables.head()) # Contains the decision variables values
print(result.optimal_outputs.head())
# Contains the objective values, target values (values that are minimized), and constraints, and extra functions.
shape: (5, 10) ┌──────────┬──────────┬──────────┬──────────┬───┬──────────┬──────────┬──────────┬──────────┐ │ x_1 ┆ x_2 ┆ x_3 ┆ x_4 ┆ … ┆ x_7 ┆ x_8 ┆ x_9 ┆ x_10 │ │ --- ┆ --- ┆ --- ┆ --- ┆ ┆ --- ┆ --- ┆ --- ┆ --- │ │ f64 ┆ f64 ┆ f64 ┆ f64 ┆ ┆ f64 ┆ f64 ┆ f64 ┆ f64 │ ╞══════════╪══════════╪══════════╪══════════╪═══╪══════════╪══════════╪══════════╪══════════╡ │ 0.000006 ┆ 0.06244 ┆ 0.48966 ┆ 0.538575 ┆ … ┆ 0.503321 ┆ 0.494747 ┆ 0.498643 ┆ 0.473955 │ │ 0.248115 ┆ 0.176653 ┆ 0.502893 ┆ 0.468368 ┆ … ┆ 0.474163 ┆ 0.498301 ┆ 0.528917 ┆ 0.526389 │ │ 0.470013 ┆ 0.756612 ┆ 0.510256 ┆ 0.515557 ┆ … ┆ 0.48843 ┆ 0.542822 ┆ 0.49639 ┆ 0.502516 │ │ 0.292879 ┆ 0.99998 ┆ 0.490506 ┆ 0.532914 ┆ … ┆ 0.473503 ┆ 0.486257 ┆ 0.528195 ┆ 0.481769 │ │ 0.661981 ┆ 0.152795 ┆ 0.512103 ┆ 0.514983 ┆ … ┆ 0.50781 ┆ 0.488249 ┆ 0.496501 ┆ 0.502518 │ └──────────┴──────────┴──────────┴──────────┴───┴──────────┴──────────┴──────────┴──────────┘ shape: (5, 7) ┌──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┐ │ g ┆ f_1 ┆ f_2 ┆ f_3 ┆ f_1_min ┆ f_2_min ┆ f_3_min │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ f64 ┆ f64 ┆ f64 ┆ f64 ┆ f64 ┆ f64 ┆ f64 │ ╞══════════╪══════════╪══════════╪══════════╪══════════╪══════════╪══════════╡ │ 0.002353 ┆ 0.997535 ┆ 0.098154 ┆ 0.00001 ┆ 0.997535 ┆ 0.098154 ┆ 0.00001 │ │ 0.003419 ┆ 0.892667 ┆ 0.254261 ┆ 0.381245 ┆ 0.892667 ┆ 0.254261 ┆ 0.381245 │ │ 0.002896 ┆ 0.276726 ┆ 0.688207 ┆ 0.674977 ┆ 0.276726 ┆ 0.688207 ┆ 0.674977 │ │ 0.003872 ┆ 0.000028 ┆ 0.899498 ┆ 0.445716 ┆ 0.000028 ┆ 0.899498 ┆ 0.445716 │ │ 0.000858 ┆ 0.492268 ┆ 0.120472 ┆ 0.863061 ┆ 0.492268 ┆ 0.120472 ┆ 0.863061 │ └──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┘
go.Figure(
go.Scatter3d(
x=result.optimal_outputs["f_1"],
y=result.optimal_outputs["f_2"],
z=result.optimal_outputs["f_3"],
mode="markers",
marker={"size": 2},
)
).show(renderer="notebook", include_plotlyjs="cdn")
Note that the results object only contains the final population of the EA.
Many users will instead want to keep an archive of solutions and use that for further analysis.
The emo_constructor automatically maintains a NonDominatedArchive, which keeps track of the non-dominated solutions found during the evolutionary process. It is available through the returned extras object as extras.archive.
Do note that as this archive is updated every generation, it runs non-domination checks many times, slowing down the process a bit.
problem = dtlz2(n_variables=10, n_objectives=3)
solver, extras = algorithms.emo_constructor(emo_options=algorithms.nsga3_options(), problem=problem)
results = solver()
# Lets plot the results. As we can see, using an archive, we get a much denser representation of the Pareto front using
# the same number of function evaluations.
go.Figure(
go.Scatter3d(
x=extras.archive.solutions["f_1"],
y=extras.archive.solutions["f_2"],
z=extras.archive.solutions["f_3"],
mode="markers",
marker={"size": 2},
)
).show(renderer="notebook", include_plotlyjs="cdn")