Welcome to desdeo-mcdm’s documentation¶
Contains interactive optimization methods for solving multiobjective optimizaton problems. This package is part of the DESDEO framework.
Requirements¶
Python 3.7 (3.8 is NOT supported at the moment).
Poetry dependency manager : Only for developers.
See pyproject.toml for Python package requirements.
Installation¶
To install and use this package on a *nix-based system, follow one of the following procedures.
For users¶
First, create a new virtual environment for the project. Then install the package using the following command:
$ pip install desdeo-mcdm
For developers¶
Download the code or clone it with the following command:
$ git clone https://github.com/industrial-optimization-group/desdeo-mcdm
Then, create a new virtual environment for the project and install the package in it:
$ cd desdeo-mcdm
$ poetry init
$ poetry install
Background concepts¶
NAUTILUS¶
In NAUTILUS, starting from the nadir point, a solution is obtained at each iteration which dominates the previous one. Although only the last solution will be Pareto optimal, the decision maker never looses sight of the Pareto optimal set, and the search is oriented so that (s)he progressively focusses on the preferred part of the Pareto optimal set. Each new solution is obtained by minimizing an achievement scalarizing function including preferences about desired improvements in objective function values.
The decision maker has two possibilities to provide her/his preferences:
The decision maker can rank the objectives according to the relative importance of improving each current objective value.
Note
This ranking is not a global preference ranking of the objectives, but represents the local importance of improving each of the current objective values at that moment.
The decision maker can specify percentages reflecting how (s)he would like to improve the current objective values, by answering to the following question:
“Assuming you have one hundred points available, how would you distribute them among the current objective values so that the more points you allocate, the more improvement on the corresponding current objective value is desired?”
After each iteration round, the decision maker specifies whether (s)he wishes to continue with the previous preference information, or define a new one.
In addition to this, the decision maker can influence the solution finding process by taking a step back to previous iteration point. This enables the decision maker to provide new preferences and change the direction of solution seeking process. Furthermore, the decision maker can also take a half-step in case (s)he feels that a full step limits the reachable area of Pareto optimal set too much.
NAUTILUS is specially suitable for avoiding undesired anchoring effects, for example in negotiation support problems, or just as a means of finding an initial Pareto optimal solution for any interactive procedure.
NIMBUS¶
As its name suggests, NIMBUS (Nondifferentiable Interactive Multiobjective BUndle-based optimization System) is a multiobjective optimization system able to handle even non-differentiable functions. It will optimize (minimize or maximize) several functions simultaneously, creating a group of different solutions. One cannot say which one of them is the best, because the system cannot know the criteria affecting the ‘goodness’ of the desired solution. The user is the one that makes the decision.
Mathematically, all the generated solutions are ‘equal’, so it is important that the user can influence the solution process. The user may want to choose which of the functions should be optimized most, the limits of the objectives, etc. In NIMBUS, this phase is called a ‘classification’. Searching for the desired solution means finding the best compromise between many different goals. If we want to get lower values for one function, we must be ready to accept the growth of another function. This is because the solutions produced by NIMBUS are Pareto optimal. This means that there is no possibility to achieve better solutions for some component of the problem without worsening some other component(s).
The Reference Point Method¶
In the Reference Point Method, the Decision Maker (DM) specifies desirable aspiration levels for objective functions. Vectors formed of these aspiration levels are then used to derive scalarizing functions having minimal values at weakly, properly or Pareto optimal solutions. It is important that reference points are intuitive and easy for the DM to specify, their consistency is not an essential requirement. Before the solution process starts, some information is given to the DM about the problem. If possible, the ideal objective vector and the (approximated) nadir objective vector are presented.
At each iteration, the DM is asked to give desired aspiration levels for the objective functions. Using this information to formulate a reference point, achievement function is minimized and a (weakly, properly or) Pareto optimal solution is obtained. This solution is then presented to the DM. In addition, k other (weakly, properly or) Pareto optimal solutions are calculated using perturbed reference points, where k is the number of objectives in the problem. The alternative solutions are also presented to the DM. If (s)he finds any of the k + 1 solutions satisfactory, the solution process is ended. Otherwise, the DM is asked to present a new reference point and the iteration described above is repeated.
The idea in perturbed reference points is that the DM gets better understanding of the possible solutions around the current solution. If the reference point is far from the Pareto optimal set, the DM gets a wider description of the Pareto optimal set and if the reference point is near the Pareto optimal set, then a finer description of the Pareto optimal set is given.
In this method, the DM has to specify aspiration levels and compare objective vectors. The DM is free to change her/his mind during the process and can direct the solution process without being forced to understand complicated concepts and their meaning. On the other hand, the method does not necessarily help the DM to find more satisfactory solutions.
NAUTILUS 2¶
Similarly to NAUTILUS, starting from the nadir point, a solution is obtained at each iteration which dominates the previous one. Although only the last solution will be Pareto optimal, the Decision Maker (DM) never looses sight of the Pareto optimal set, and the search is oriented so that (s)he progressively focusses on the preferred part of the Pareto optimal set. Each new solution is obtained by minimizing an achievement scalarizing function including preferences about desired improvements in objective function values.
NAUTILUS 2 introduces a new preference handling technique which is easily understandable for the DM and allows the DM to conveniently control the solution process. Preferences are given as direction of improvement for objectives. In NAUTILUS 2, the DM has three ways to do this:
The DM sets the direction of improvement directly.
The DM defines the improvement ratio between two different objectives \(f_i\) and \(f_j\). For example, if the DM wishes that the improvement of fi by one unit should be accompanied with the improvement of \(f_j\) by \(θ_{ij}\) units. Here, the DM selects an objective \(f_{i} (i=1,…,k)\) and for each of the other objectives \(f_j\) sets the value \(θ_{ij}\). Then, the direction of improvement is defined by
As a generalization of the approach 2, the DM sets values of improvement ratios freely for some selected pairs of objective functions.
As with NAUTILUS, after each iteration round, the decision maker specifies whether (s)he wishes to continue with the previous preference information, or define a new one.
In addition to this, the decision maker can influence the solution finding process by taking a step back to the previous iteration point. This enables the decision maker to provide new preferences and change the direction of the solution seeking process. Furthermore, the decision maker can also take a half-step in case (s)he feels that a full step limits the reachable area of the Pareto optimal set too much.
API Documentation¶
desdeo_mcdm.interactive Package¶
This module contains interactive methods and related requests implemented as classes.
Functions¶
|
Validate decision maker’s response. |
|
Validate decision maker’s preferences. |
|
Validate decision maker’s preferences in NAUTILUS 2. |
|
Validate decision maker’s preference for number of iterations. |
Classes¶
|
|
Raised when an exception related to ENautilus is encountered. |
|
|
A request class to handle the initial preferences. |
|
A request class to handle the intermediate requests. |
|
A request class to handle termination. |
|
Implements the basic NAUTILUS method as presented in |Miettinen_2010|. |
|
Implements the NAUTILUS 2 method as presented in |Miettinen_2015|. |
Raised when an exception related to Nautilus is encountered. |
|
|
A request class to handle the Decision maker’s initial preferences for the first iteration round. |
|
A request class to handle the Decision maker’s preferences after the first iteration round. |
|
A request class to handle termination. |
|
Implementations of the NAUTILUS Navigator algorithm. |
Raised when an exception related to NAUTILUS Navigator is encountered. |
|
|
Request to handle interactions with NAUTILUS Navigator. |
|
Implements the synchronous NIMBUS algorithm. |
Risen when an error related to NIMBUS is encountered. |
|
|
A request to handle the classification of objectives in the synchronous NIMBUS method. |
A request to handle the computation of intermediate points between two previously computed points. |
|
|
A request to handle the indication of a preferred point. |
|
A request to handle archiving of the solutions computed with NIMBUS. |
|
A request to handle the termination of Synchronous NIMBUS. |
|
Implements the Reference Point Method as presented in |Wierzbicki_1982|. |
Raised when an exception related to Reference Point Method (RFM) is encountered. |
|
|
A request class to handle the Decision Maker’s initial preferences for the first iteration round. |
|
A request class to handle the Decision Maker’s preferences after the first iteration round. |
|
A request class to handle termination. |
Class Inheritance Diagram¶

desdeo_mcdm.utilities Package¶
This module contains various utilities used in different interactive methods.
Functions¶
|
Uses the payoff table method to solve for the ideal and nadir points of a MOProblem. |
|
Solves a representation for the nadir and ideal points for a multiobjective minimization problem with objectives defined as the result of some objective evaluator. |
|
Pass through to solve_pareto_front_representation_general when the problem for which the front is being calculated for is defined as an MOProblem object. |
Computes a representation of a Pareto efficient front from a multiobjective minimizatino problem. |
|
|
A simple linear weight based scalarizer. |
Examples¶
Example on the usage of NIMBUS¶
This notebook will go through a simple example to illustrate how the synchronous variant of NIMBUS has been implemented in the DESDEO framework.
We will be solving the Kursawe function originally defined in this article
Let us begin by importing some libraries and defining the problem.
[1]:
import numpy as np
import matplotlib.pyplot as plt
from desdeo_problem.Problem import MOProblem
from desdeo_problem.Variable import variable_builder
from desdeo_problem.Objective import _ScalarObjective
def f_1(xs: np.ndarray):
xs = np.atleast_2d(xs)
xs_plusone = np.roll(xs, 1, axis=1)
return np.sum(-10*np.exp(-0.2*np.sqrt(xs[:, :-1]**2 + xs_plusone[:, :-1]**2)), axis=1)
def f_2(xs: np.ndarray):
xs = np.atleast_2d(xs)
return np.sum(np.abs(xs)**0.8 + 5*np.sin(xs**3), axis=1)
varsl = variable_builder(
["x_1", "x_2", "x_3"],
initial_values=[0, 0, 0],
lower_bounds=[-5, -5, -5],
upper_bounds=[5, 5, 5],
)
f1 = _ScalarObjective(name="f1", evaluator=f_1)
f2 = _ScalarObjective(name="f2", evaluator=f_2)
problem = MOProblem(variables=varsl, objectives=[f1, f2], ideal=np.array([-20, -12]), nadir=np.array([-14, 0.5]))
To check out problem, let us compute a representation of the Pareto optimal front of solutions:
[2]:
from desdeo_mcdm.utilities.solvers import solve_pareto_front_representation
p_front = solve_pareto_front_representation(problem, step=1.0)[1]
plt.scatter(p_front[:, 0], p_front[:, 1], label="Pareto front")
plt.scatter(problem.ideal[0], problem.ideal[1], label="Ideal")
plt.scatter(problem.nadir[0], problem.nadir[1], label="Nadir")
plt.xlabel("f1")
plt.ylabel("f2")
plt.title("Approximate Pareto front of the Kursawe function")
plt.legend()
plt.show()
Now we can get to the NIMBUS part. Let us define an instance of the NIMBUS method utilizing our problem defined earlier, and start by invoking the instance’s start
method:
[3]:
from desdeo_mcdm.interactive.NIMBUS import NIMBUS
method = NIMBUS(problem, "scipy_de")
classification_request, plot_request = method.start()
Let us look at the keys in the dictionary contained in the classification_request
:
[4]:
print(classification_request.content.keys())
dict_keys(['message', 'objective_values', 'classifications', 'levels', 'number_of_solutions'])
Message should give us some more information:
[5]:
print(classification_request.content["message"])
Please classify each of the objective values in one of the following categories:
1. values should improve '<'
2. values should improve until some desired aspiration level is reached '<='
3. values with an acceptable level '='
4. values which may be impaired until some upper bound is reached '>='
5. values which are free to change '0'
Provide the aspiration levels and upper bounds as a vector. For categories 1, 3, and 5,the value in the vector at the objective's position is ignored. Suppy also the number of maximumsolutions to be generated.
We should therefore classify each of the objectives found beind the objective_values
-key in the dictionary in classification_request.content
. Let’s print them:
[6]:
print(classification_request.content["objective_values"])
[-15.88641547 -7.74757837]
Instead of printing the values, we could have also used the plot_request
object. However, we are inspecting only one set of objective values for the time being, so a raw print of the values should be enough. Let us classify the objective values next. We can get a hint of what the classification should look like by inspecting the value found using the classifications
-key in classification_request.content
:
[7]:
print(classification_request.content["classifications"])
[None]
Therefore it should be a list. Suppose we wish to improve (decrease in value) the first objective, and impair (increase in value) the second objective till some upper bound is reached. We should define our preferences as a dictionary classification_request.response
with the keys classifications
and number_of_solutions
(we have to define the number of new solutions we wish to compute). The key levels
will contain the upper bound for the second objective.
[8]:
response = {
"classifications": ["<", ">="],
"number_of_solutions": 3,
"levels": [0, -5]
}
classification_request.response = response
To continue, just feed classification_request
back to the method through the step
method:
[9]:
save_request, plot_request = method.iterate(classification_request)
We got a new request as a response. Let us inspect it:
[10]:
print(save_request.content.keys())
print(save_request.content["message"])
print(save_request.content["objectives"])
dict_keys(['message', 'solutions', 'objectives', 'indices'])
Please specify which solutions shown you would like to save for later viewing. Supply the indices of such solutions as a list, or supply an empty list if none of the shown soulutions should be saved.
[array([-1.99999999e+01, 2.34193302e-06]), array([-1.99999998e+01, 3.34684282e-06]), array([-18.46633851, -1.81760788])]
Suppose the first and last solutions result in nice objective values.
[11]:
response = {"indices": [0, 2]}
save_request.response = response
intermediate_request, plot_request = method.iterate(save_request)
[12]:
print(intermediate_request.content.keys())
print(intermediate_request.content["message"])
dict_keys(['message', 'solutions', 'objectives', 'indices', 'number_of_desired_solutions'])
Would you like to see intermediate solutions between two previusly computed solutions? If so, please supply two indices corresponding to the solutions.
We do not desire to see intermediate results.
[13]:
response = {"number_of_desired_solutions": 0, "indices": []}
intermediate_request.response = response
preferred_request, plot_request = method.iterate(intermediate_request)
[14]:
print(preferred_request.content.keys())
print(preferred_request.content["message"])
dict_keys(['message', 'solutions', 'objectives', 'index', 'continue'])
Please select your most preferred solution and whether you would like to continue.
We should select our most preferred solution. Let us plot the objective values to inspect them better:
[15]:
plt.scatter(p_front[:, 0], p_front[:, 1], label="Pareto front")
plt.scatter(problem.ideal[0], problem.ideal[1], label="Ideal")
plt.scatter(problem.nadir[0], problem.nadir[1], label="Nadir")
for i, z in enumerate(preferred_request.content["objectives"]):
plt.scatter(z[0], z[1], label=f"solution {i}")
plt.xlabel("f1")
plt.ylabel("f2")
plt.title("Approximate Pareto front of the Kursawe function")
plt.legend()
plt.show()
Solutions at indices 0 and 2 seem to be overlapping in the objective space. We decide to select the solution at index 1, and to continue the iterations.
[16]:
response = {"index": 1, "continue": True}
preferred_request.response = response
classification_request, plot_request = method.iterate(preferred_request)
Back at the classification pahse of the NIMBUS method.
[17]:
response = {
"classifications": [">=", "<"],
"number_of_solutions": 4,
"levels": [-16, -1]
}
classification_request.response = response
save_request, plot_request = method.iterate(classification_request)
Let us plot some of the solutions again:
[18]:
plt.scatter(p_front[:, 0], p_front[:, 1], label="Pareto front")
plt.scatter(problem.ideal[0], problem.ideal[1], label="Ideal")
plt.scatter(problem.nadir[0], problem.nadir[1], label="Nadir")
for i, z in enumerate(save_request.content["objectives"]):
plt.scatter(z[0], z[1], label=f"solution {i}")
plt.xlabel("f1")
plt.ylabel("f2")
plt.title("Approximate Pareto front of the Kursawe function")
plt.legend()
plt.show()
NIMBUS really took to heart our request to detoriate the first objective… Suppose we like all of the solutions:
[19]:
response = {"indices": [0, 1, 2, 3]}
save_request.response = response
intermediate_request, plot_request = method.iterate(save_request)
Let us plot everything we have so far:
[20]:
plt.scatter(p_front[:, 0], p_front[:, 1], label="Pareto front")
plt.scatter(problem.ideal[0], problem.ideal[1], label="Ideal")
plt.scatter(problem.nadir[0], problem.nadir[1], label="Nadir")
for i, z in enumerate(intermediate_request.content["objectives"]):
plt.scatter(z[0], z[1], label=f"solution {i}")
plt.xlabel("f1")
plt.ylabel("f2")
plt.title("Approximate Pareto front of the Kursawe function")
plt.legend()
plt.show()
Assume we really like what we have between solution 3 and 4. Let NIMBUS compute 3 intermediate solutions between them:
[21]:
response = {
"indices": [3, 4],
"number_of_desired_solutions": 3,
}
intermediate_request.response = response
save_request, plot_request = method.iterate(intermediate_request)
Plot the intermediate solutions:
[22]:
plt.scatter(p_front[:, 0], p_front[:, 1], label="Pareto front")
plt.scatter(problem.ideal[0], problem.ideal[1], label="Ideal")
plt.scatter(problem.nadir[0], problem.nadir[1], label="Nadir")
for i, z in enumerate(save_request.content["objectives"]):
plt.scatter(z[0], z[1], label=f"solution {i}")
plt.xlabel("f1")
plt.ylabel("f2")
plt.title("Approximate Pareto front of the Kursawe function")
plt.legend()
plt.show()
Nice, we are really getting there, even if we have no goal set… Let us save solution 1:
[23]:
response = {"indices": [1]}
save_request.response = response
intermediate_request, plot_request = method.iterate(save_request)
We do not wish to generate any more intermediate solutions.
[24]:
response = {"number_of_desired_solutions": 0, "indices": []}
intermediate_request.response = response
preferred_request, plot_request = method.iterate(intermediate_request)
Let us plot everything we have, and select a final solution:
[25]:
plt.scatter(p_front[:, 0], p_front[:, 1], label="Pareto front")
plt.scatter(problem.ideal[0], problem.ideal[1], label="Ideal")
plt.scatter(problem.nadir[0], problem.nadir[1], label="Nadir")
for i, z in enumerate(preferred_request.content["objectives"]):
plt.scatter(z[0], z[1], label=f"solution {i}")
plt.xlabel("f1")
plt.ylabel("f2")
plt.title("Approximate Pareto front of the Kursawe function")
plt.legend()
plt.show()
We REALLY like solution 6, so let us go with that:
[26]:
response = {
"index": 6,
"continue": False,
}
preferred_request.response = response
print("hello")
print(preferred_request)
stop_request, plot_request = method.iterate(preferred_request)
print(stop_request)
hello
<desdeo_mcdm.interactive.NIMBUS.NimbusMostPreferredRequest object at 0x7f5078418940>
<desdeo_mcdm.interactive.NIMBUS.NimbusStopRequest object at 0x7f507a67b670>
We are done, let us bask in the glory of the solution found:
[27]:
print(f"Final decision variables: {stop_request.content['solution']}")
plt.scatter(p_front[:, 0], p_front[:, 1], label="Pareto front")
plt.scatter(problem.ideal[0], problem.ideal[1], label="Ideal")
plt.scatter(problem.nadir[0], problem.nadir[1], label="Nadir")
plt.scatter(stop_request.content["objective"][0], stop_request.content["objective"][1], label=f"final solution")
plt.xlabel("f1")
plt.ylabel("f2")
plt.title("Approximate Pareto front of the Kursawe function")
plt.legend()
plt.show()
Final decision variables: [-1.02767713 -1.09988959 -1.09984277]
Currently implemented methods¶
Algorithm |
Reference |
---|---|
Synchronous NIMBUS |
Miettinen, K., Mäkelä, M.M.: Synchronous approach in interactive multiobjective optimization. Eur. J. Oper. Res. 170(3), 909–922 (2006) |
NAUTILUS Navigator |
Ruiz, A. B., Ruiz, F., Miettinen, K., Delgado-Antequera, L., & Ojalehto, V. (2019). NAUTILUS Navigator : free search interactive multiobjective optimization without trading-off. Journal of Global Optimization, 74 (2), 213-231. doi:10.1007/s10898-019-00765-2 |
E-NAUTILUS |
Ruiz, A., Sindhya, K., Miettinen, K., Ruiz, F., & Luque, M. (2015). E-NAUTILUS: A decision support system for complex multiobjective optimization problems based on the NAUTILUS method. European Journal of Operational Research, 246 (1), 218-231. doi:10.1016/j.ejor.2015.04.027 |
NAUTILUS |
Kaisa Miettinen, Petri Eskelinen, Francisco Ruiz, Mariano Luque, NAUTILUS method: An interactive technique in multiobjective optimization based on the nadir point, European Journal of Operational Research, Volume 206, Issue 2, 2010, Pages 426-434, ISSN 0377-2217, https://doi.org/10.1016/j.ejor.2010.02.041. |
Reference Point Method |
Andrzej P. Wierzbicki, A mathematical basis for satisficing decision making, Mathematical Modelling, Volume 3, Issue 5,1982, Pages 391-405, ISSN 0270-0255, https://doi.org/10.1016/0270-0255(82)90038-0. |
NAUTILUSv2 |
Miettinen, K., Podkopaev, D., Ruiz, F. et al. A new preference handling technique for interactive multiobjective optimization without trading-off. J Glob Optim 63, 633–652 (2015). https://doi.org/10.1007/s10898-015-0301-8 |
Coming soon¶
Pareto Navigator