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 0x7fca70bdfac0>
dict_keys(['message', 'ideal', 'nadir', 'reachable_lb', 'reachable_ub', 'user_bounds', '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 them 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 towards 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. Bounds for one or more objectives may also be specified as 'user_bounds'; when navigating,the value of the objectives present in the navigation points will not exceed the valuesspecified in 'user_bounds'.Lastly, if stopping is desired, `stop` should be True, otherwise 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.]
[7]:
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, user_bounds=[None, None])

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.

[8]:
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.

[9]:
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 has narrowed down. Suppose now we wish to return to a previous step and change our preferences. Let’s say, step 14.

[10]:
# 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 that 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.

[11]:
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.

[12]:
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 elsewhere. The final index returned can then be used to find the corresponding decision variables to the found solution in objective space.