-
Notifications
You must be signed in to change notification settings - Fork 449
Description
Hello my optimization fellows,
for my thesis I'm currently implementing the CEC2018 DF Benchmark Suite in Pymoo
So far I defined following problem:
from pymoo.core.problem import Problem
import numpy as np
from numpy import sin, pi, floor, power
class DF1(Problem):
def __init__(self, taut=30, nt=10, n_var=10):
super().__init__(n_var=n_var, n_obj=2, n_constr=0, xl=0.0, xu=1.0)
self.tau = 0 # generation counter
self.taut = taut # frequency of change
self.nt = nt # severity of change
self.T0 = 50 # first change occurs after T0 generations
self.t = 0 # time
def _update_t(self):
tau_tmp = max(self.tau + self.taut - (self.T0 + 1), 0)
self.t = (1.0 / self.nt) * floor(tau_tmp / self.taut)
def _calc_pareto_front(self, n_pareto_points=100):
# create numpy array from 0 to 1 with n_pareto_points
x = np.linspace(0, 1, n_pareto_points)
# TODO: validate 1 - transposal
return 1 - np.array([x, 1 - x ** self._H()]).T
def _g(self):
G = abs(sin(0.5 * pi * self.t))
return G
def _H(self):
H = 0.75 * sin(0.5 * pi * self.t) + 1.25
return H
def _evaluate(self, x, out, *args, **kwargs):
# update time instant
self._update_t()
G = self._g()
H = self._H()
F1 = []
F2 = []
# evaluate fitness for each individual and append to result list
for individual in x:
g = 1 + sum((individual[1:] - G) ** 2)
f1 = individual[0]
f2 = g * power(1 - (individual[0] / g), H)
F1.append(f1)
F2.append(f2)
# increase generation counter
self.tau += 1
out["F"] = np.column_stack([F1, F2])Now, one can test this problem with the following code. I am using the ask and tell interface as I also want to implement dynamic multi objective optimization algorithms that relocate the population after change in the environment happens.
from pymoo.algorithms.moo.nsga2 import NSGA2
from benchmarks import DF1
problem = DF1()
algorithm = NSGA2(pop_size=100)
# prepare the algorithm to solve the specific problem (same arguments as for the minimize function)
algorithm.setup(problem, ("n_gen", 500), seed=1, verbose=True)
# until the algorithm has no terminated
while algorithm.has_next():
# ask the algorithm for the next solution to be evaluated
pop = algorithm.ask()
# evaluate the individuals using the algorithm's evaluator (necessary to count evaluations for termination)
algorithm.evaluator.eval(problem, pop)
# returned the evaluated individuals which have been evaluated or even modified
algorithm.tell(infills=pop)
# obtain the result objective from the algorithm
res = algorithm.result()Examining the output, one can notice that performance metrics are updated only every 30 generations, as the problems fitnessfunction changes every 30 generations.
============================================================
n_gen | n_eval | igd | gd | hv
============================================================
48 | 4800 | 0.005307900 | 0.004815726 | 0.546348513
49 | 4900 | 0.005361608 | 0.004736356 | 0.546309798
50 | 5000 | 0.005008034 | 0.004418473 | 0.546871336
51 | 5100 | 0.004842350 | 0.004153299 | 0.547019545
52 | 5200 | 0.004842350 | 0.004153299 | 0.547019545
53 | 5300 | 0.004842350 | 0.004153299 | 0.547019545
54 | 5400 | 0.004842350 | 0.004153299 | 0.547019545
55 | 5500 | 0.004842350 | 0.004153299 | 0.547019545
56 | 5600 | 0.004842350 | 0.004153299 | 0.547019545
57 | 5700 | 0.004842350 | 0.004153299 | 0.547019545
58 | 5800 | 0.004842350 | 0.004153299 | 0.547019545
59 | 5900 | 0.004842350 | 0.004153299 | 0.547019545
60 | 6000 | 0.004842350 | 0.004153299 | 0.547019545
61 | 6100 | 0.004842350 | 0.004153299 | 0.547019545
62 | 6200 | 0.004842350 | 0.004153299 | 0.547019545
63 | 6300 | 0.004842350 | 0.004153299 | 0.547019545
64 | 6400 | 0.004842350 | 0.004153299 | 0.547019545
65 | 6500 | 0.004842350 | 0.004153299 | 0.547019545
66 | 6600 | 0.004842350 | 0.004153299 | 0.547019545
67 | 6700 | 0.004842350 | 0.004153299 | 0.547019545
68 | 6800 | 0.004842350 | 0.004153299 | 0.547019545
69 | 6900 | 0.004842350 | 0.004153299 | 0.547019545
70 | 7000 | 0.004842350 | 0.004153299 | 0.547019545
71 | 7100 | 0.004842350 | 0.004153299 | 0.547019545
72 | 7200 | 0.004842350 | 0.004153299 | 0.547019545
73 | 7300 | 0.004842350 | 0.004153299 | 0.547019545
74 | 7400 | 0.004842350 | 0.004153299 | 0.547019545
75 | 7500 | 0.004842350 | 0.004153299 | 0.547019545
76 | 7600 | 0.004842350 | 0.004153299 | 0.547019545
77 | 7700 | 0.004842350 | 0.004153299 | 0.547019545
78 | 7800 | 0.004846134 | 0.006071532 | 0.547014882
79 | 7900 | 0.004846134 | 0.006071532 | 0.547014882
80 | 8000 | 0.004846134 | 0.005925610 | 0.547014882
81 | 8100 | 0.004846134 | 0.005925610 | 0.547014882
82 | 8200 | 0.004846134 | 0.005925610 | 0.547014882
83 | 8300 | 0.004846134 | 0.005925610 | 0.547014882
84 | 8400 | 0.004846134 | 0.005925610 | 0.547014882
85 | 8500 | 0.004846134 | 0.005925610 | 0.547014882
86 | 8600 | 0.004846134 | 0.005925610 | 0.547014882
87 | 8700 | 0.004846134 | 0.005925610 | 0.547014882
88 | 8800 | 0.004846134 | 0.005925610 | 0.547014882
89 | 8900 | 0.004846134 | 0.005925610 | 0.547014882
90 | 9000 | 0.004846134 | 0.005925610 | 0.547014882
91 | 9100 | 0.004846134 | 0.005925610 | 0.547014882
92 | 9200 | 0.004846134 | 0.005925610 | 0.547014882
93 | 9300 | 0.004846134 | 0.005925610 | 0.547014882
94 | 9400 | 0.004846134 | 0.005925610 | 0.547014882
95 | 9500 | 0.004846134 | 0.005925610 | 0.547014882
96 | 9600 | 0.004846134 | 0.005925610 | 0.547014882
97 | 9700 | 0.004846134 | 0.005925610 | 0.547014882
98 | 9800 | 0.004846134 | 0.005925610 | 0.547014882
99 | 9900 | 0.004846134 | 0.005925610 | 0.547014882
At this point, my question.
Shouldn't the metrics still at least somewhat change every generation, as the fitness function of the DF1 Problem prefers other solutions? To me, it looks like NSGA doesn't follow the new Pareto-Front at all.
I also attached some visualizations to see my concern.
visualisation.zip
Is this a bug or am I misunderstanding something and this is just how NSGA-II should work here?
I would be really thankful for any kind of help or clarification here, thanks!