Welcome to desdeo-mcdm’s documentation

Contains interactive optimization methods for solving multiobjective optimizaton problems. This package is part of the DESDEO framework.

Requirements

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:

  1. 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.

  1. 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:

  1. The DM sets the direction of improvement directly.

  2. 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

\[δ_i=1\ and\ δ_j=θ_{ij},\ j≠i.\]
  1. 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_response(n_objectives, z_current, …)

Validate decision maker’s response.

validate_preferences(n_objectives, response)

Validate decision maker’s preferences.

validate_n2_preferences(n_objectives, response)

Validate decision maker’s preferences in NAUTILUS 2.

validate_n_iterations(n_it)

Validate decision maker’s preference for number of iterations.

Classes

ENautilus(pareto_front, ideal, nadir[, …])

ENautilusException

Raised when an exception related to ENautilus is encountered.

ENautilusInitialRequest(ideal, nadir)

A request class to handle the initial preferences.

ENautilusRequest(ideal, nadir, points, …)

A request class to handle the intermediate requests.

ENautilusStopRequest(preferred_point)

A request class to handle termination.

Nautilus(problem, ideal, nadir[, epsilon, …])

Implements the basic NAUTILUS method as presented in |Miettinen_2010|.

NautilusV2(problem, starting_point, ideal, nadir)

Implements the NAUTILUS 2 method as presented in |Miettinen_2015|.

NautilusException

Raised when an exception related to Nautilus is encountered.

NautilusInitialRequest(ideal, nadir)

A request class to handle the Decision maker’s initial preferences for the first iteration round.

NautilusRequest(z_current, nadir, …)

A request class to handle the Decision maker’s preferences after the first iteration round.

NautilusStopRequest(x_h, f_h)

A request class to handle termination.

NautilusNavigator(pareto_front, ideal, nadir)

Implementations of the NAUTILUS Navigator algorithm.

NautilusNavigatorException

Raised when an exception related to NAUTILUS Navigator is encountered.

NautilusNavigatorRequest(ideal, nadir, …)

Request to handle interactions with NAUTILUS Navigator.

NIMBUS(problem[, scalar_method])

Implements the synchronous NIMBUS algorithm.

NimbusException

Risen when an error related to NIMBUS is encountered.

NimbusClassificationRequest(method, ref)

A request to handle the classification of objectives in the synchronous NIMBUS method.

NimbusIntermediateSolutionsRequest(…)

A request to handle the computation of intermediate points between two previously computed points.

NimbusMostPreferredRequest(solution_vectors, …)

A request to handle the indication of a preferred point.

NimbusSaveRequest(solution_vectors, …)

A request to handle archiving of the solutions computed with NIMBUS.

NimbusStopRequest(solution_final, …)

A request to handle the termination of Synchronous NIMBUS.

ReferencePointMethod(problem, ideal, nadir)

Implements the Reference Point Method as presented in |Wierzbicki_1982|.

RPMException

Raised when an exception related to Reference Point Method (RFM) is encountered.

RPMInitialRequest(ideal, nadir)

A request class to handle the Decision Maker’s initial preferences for the first iteration round.

RPMRequest(f_current, f_additionals, ideal, …)

A request class to handle the Decision Maker’s preferences after the first iteration round.

RPMStopRequest(x_h, f_h)

A request class to handle termination.

Class Inheritance Diagram
Inheritance diagram of desdeo_mcdm.interactive.ENautilus.ENautilus, desdeo_mcdm.interactive.ENautilus.ENautilusException, desdeo_mcdm.interactive.ENautilus.ENautilusInitialRequest, desdeo_mcdm.interactive.ENautilus.ENautilusRequest, desdeo_mcdm.interactive.ENautilus.ENautilusStopRequest, desdeo_mcdm.interactive.Nautilus.Nautilus, desdeo_mcdm.interactive.NautilusV2.NautilusV2, desdeo_mcdm.interactive.NautilusV2.NautilusException, desdeo_mcdm.interactive.NautilusV2.NautilusInitialRequest, desdeo_mcdm.interactive.NautilusV2.NautilusRequest, desdeo_mcdm.interactive.NautilusV2.NautilusStopRequest, desdeo_mcdm.interactive.NautilusNavigator.NautilusNavigator, desdeo_mcdm.interactive.NautilusNavigator.NautilusNavigatorException, desdeo_mcdm.interactive.NautilusNavigator.NautilusNavigatorRequest, desdeo_mcdm.interactive.NIMBUS.NIMBUS, desdeo_mcdm.interactive.NIMBUS.NimbusException, desdeo_mcdm.interactive.NIMBUS.NimbusClassificationRequest, desdeo_mcdm.interactive.NIMBUS.NimbusIntermediateSolutionsRequest, desdeo_mcdm.interactive.NIMBUS.NimbusMostPreferredRequest, desdeo_mcdm.interactive.NIMBUS.NimbusSaveRequest, desdeo_mcdm.interactive.NIMBUS.NimbusStopRequest, desdeo_mcdm.interactive.ReferencePointMethod.ReferencePointMethod, desdeo_mcdm.interactive.ReferencePointMethod.RPMException, desdeo_mcdm.interactive.ReferencePointMethod.RPMInitialRequest, desdeo_mcdm.interactive.ReferencePointMethod.RPMRequest, desdeo_mcdm.interactive.ReferencePointMethod.RPMStopRequest

desdeo_mcdm.utilities Package

This module contains various utilities used in different interactive methods.

Functions

payoff_table_method(problem[, …])

Uses the payoff table method to solve for the ideal and nadir points of a MOProblem.

payoff_table_method_general(…[, …])

Solves a representation for the nadir and ideal points for a multiobjective minimization problem with objectives defined as the result of some objective evaluator.

solve_pareto_front_representation(problem[, …])

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.

solve_pareto_front_representation_general(…)

Computes a representation of a Pareto efficient front from a multiobjective minimizatino problem.

weighted_scalarizer(xs, ws)

A simple linear weight based scalarizer.

Examples

NAUTILUS Navigator example

This example goes through the basic functionalities of the NAUTILUS Navigator method.

We will consider a simple 2D Pareto front which we will define next alongside the method itself. Both objectives are to be minimized.

Because of the nature of navigation based interactive optimization methods, the idea of NAUTILUS Navigator is best demonstrated using some graphical user interface. One such interface can be found online.

[1]:
import numpy as np
from desdeo_mcdm.interactive.NautilusNavigator import NautilusNavigator

# half of a parabola to act as a Pareto front
f1 = np.linspace(1, 100, 50)
f2 = f1[::-1] ** 2

front = np.stack((f1, f2)).T
ideal = np.min(front, axis=0)
nadir = np.max(front, axis=0)

method = NautilusNavigator((front), ideal, nadir)

To start, we can invoke the start method.

[2]:
req_first = method.start()

print(req_first)
print(req_first.content.keys())
<desdeo_mcdm.interactive.NautilusNavigator.NautilusNavigatorRequest object at 0x7ff8807cfd60>
dict_keys(['message', 'ideal', 'nadir', 'reachable_lb', 'reachable_ub', 'reachable_idx', 'step_number', 'steps_remaining', 'distance', 'allowed_speeds', 'current_speed', 'navigation_point'])

The returned object is a NautilusNavigatorRequest. The keys should give an idea of what the contents of the request are. We will explain most of the in this example.

At the moment, the nadir, reachable_lb and reachable_ub are most interesting to us. Navigation starts from the nadir and will proceed toward the Pareto optimal front enclosed between the limits defined in reachable_lb and reachable_ub.

To interact with the method, we must fill out the response member of req. Let’s see the contents of the message in req next.

[3]:
print(req_first.content["message"])
Please supply aspirations levels for each objective between the upper and lower bounds as `reference_point`. Specify a speed between 1-5 as `speed`. If going to a previous step is desired, please set `go_to_previous` to True, otherwise it should be False. Lastly, if stopping is desired, `stop` should be True, otherweise it should be set to False.

We should define the required values and set them as keys of a dictionary. Before that, it is useful to see the bounds to know the currently feasible objective values.

[4]:
print(req_first.content["reachable_lb"])
print(req_first.content["reachable_ub"])
[1. 1.]
[  100. 10000.]
[5]:
reference_point = np.array([50, 6000])
go_to_previous = False
stop = False
speed = 1

response = dict(reference_point=reference_point, go_to_previous=False, stop=False, speed=1)

go_to_previous should be set to False unless we desire going to a previous point. stop should be True if we wish to stop, otherwise it should be False. speed is the speed of the navigation. It is not used internally in the method. To continue, we call iterate with suppliying the req object with a defined response attribute. We should get a new request as a return value.

[6]:
req_first.response = response
req_snd = method.iterate(req_first)

print(req_snd.content["reachable_lb"])
print(req_snd.content["reachable_ub"])
[3.02040816 9.12286547]
[  100. 10000.]

We see that the bounds have narrowed down as they should.

In reality, iterate should be called multiple times in succession with the same response contents. We can do this in a loop until the 30th step is computed, for example. NB: Steps are internally zero-index based.

[7]:
previous_requests = [req_first, req_snd]
req = req_snd
while method._step_number < 30:
    req.response = response
    req = method.iterate(req)

    previous_requests.append(req)

print(req.content["reachable_lb"])
print(req.content["reachable_ub"])
print(req.content["step_number"])
[ 11.10204082 449.61307788]
[  81.81632653 8081.64306539]
30

The region of reachable Pareto optimal solutions had narrowed down. Suppose now we wish to return to a previous step and change our preferences. Let’s say, step 14.

[8]:
# fetch the 14th step saved previously
req_14 = previous_requests[13]
print(req_14.content["reachable_lb"])
print(req_14.content["reachable_ub"])
print(req_14.content["step_number"])

req_14.response["go_to_previous"] = True
req_14.response["reference_point"] = np.array([50, 5000])
new_response = req_14.response
[  5.04081633 123.25531029]
[  91.91836735 9208.16493128]
14

When going to a previous point, the method assumes thath the state the method was in during that point is fully defined in the request object given to it when calling iterate with go_to_previous being True. This is why we saved the request previously in a list.

[9]:
req_14_new = method.iterate(req_14)
req = req_14_new

# remember to unser go_to_previous!
new_response["go_to_previous"] = False

# continue iterating for 16 steps
while method._step_number < 30:
    req.response = new_response
    req = method.iterate(req)

print("Old 30th step")
print(previous_requests[29].content["reachable_lb"])
print(previous_requests[29].content["reachable_ub"])
print(previous_requests[29].content["step_number"])

print("New 30th step")
print(req.content["reachable_lb"])
print(req.content["reachable_ub"])
print(req.content["step_number"])
Old 30th step
[ 11.10204082 449.61307788]
[  81.81632653 8081.64306539]
30
New 30th step
[ 11.10204082 368.01332778]
[  81.81632653 8081.64306539]
30

We can see a difference in the limits when we changed the preference point.

To find the final solution, we can iterate till the end.

[10]:
while method._step_number < 100:
    req.response = new_response
    req = method.iterate(req)

print(req.content["reachable_idx"])
19

When finished navigating, the method will return the index of the reached solution based on the supplied Pareto front. It is assumed that if decision variables also exist for the problem, they are stored elwhere. The final index returned can then be used to find the corresponding decision variables to the found solution in objective space.

[ ]:

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()
_images/notebooks_synchronous_nimbus_3_0.svg

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()
_images/notebooks_synchronous_nimbus_27_0.svg

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()
_images/notebooks_synchronous_nimbus_33_0.svg

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()
_images/notebooks_synchronous_nimbus_37_0.svg

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()
_images/notebooks_synchronous_nimbus_41_0.svg

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()
_images/notebooks_synchronous_nimbus_47_0.svg

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]
_images/notebooks_synchronous_nimbus_51_1.svg

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

Indices and tables