Skip to content

Commit

Permalink
Merge pull request #380 from zStupan/feature-convergence
Browse files Browse the repository at this point in the history
Modify convergence plotting
  • Loading branch information
firefly-cpp authored Mar 4, 2022
2 parents 25c3c8e + 5b2c174 commit 5e67e77
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 31 deletions.
1 change: 1 addition & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@ pandoc = "*"
twine = "*"
pytest-testmon = "~=1.2.2"
pytest-custom-exit-code = {git = "https://github.com/GregaVrbancic/pytest-custom_exit_code.git"}
bandit = "<1.7.3"
2 changes: 1 addition & 1 deletion examples/log_results.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@
task = Task(max_evals=10000, problem=Sphere(dimension=10))
algo = DifferentialEvolution(population_size=40, crossover_probability=0.9, differential_weight=0.5)
best = algo.run(task)
evals, x_f = task.return_conv()
evals, x_f = task.convergence_data(x_axis='evals')
print(evals) # print function evaluations
print(x_f) # print values
2 changes: 1 addition & 1 deletion examples/run_cso.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@
best = algo.run(task=task)
print('%s -> %s' % (best[0], best[1]))
# plot a convergence graph
task.plot()
task.plot_convergence(x_axis='evals')
73 changes: 47 additions & 26 deletions niapy/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import numpy as np
from matplotlib import pyplot as plt
import matplotlib.ticker as ticker
from niapy.problems import Problem
from niapy.util.repair import limit
from niapy.util.factory import get_problem
Expand Down Expand Up @@ -99,7 +100,8 @@ def __init__(self, problem=None, dimension=None, lower=None, upper=None,
self.max_evals = max_evals
self.max_iters = max_iters
self.n_evals = []
self.x_f_vals = []
self.fitness_evals = [] # fitness improvements at self.n_evals evaluations
self.fitness_iters = [] # best fitness at each iteration

def repair(self, x, rng=None):
r"""Repair solution and put the solution in the random position inside of the bounds of problem.
Expand All @@ -123,6 +125,7 @@ def repair(self, x, rng=None):

def next_iter(self):
r"""Increments the number of algorithm iterations."""
self.fitness_iters.append(self.x_f)
self.iters += 1

def eval(self, x):
Expand All @@ -144,10 +147,9 @@ def eval(self, x):
if x_f < self.x_f * self.optimization_type.value:
self.x_f = x_f * self.optimization_type.value
self.n_evals.append(self.evals)
self.x_f_vals.append(x_f)
self.fitness_evals.append(x_f)
if self.enable_logging:
logger.info('evals:%d => %s' % (self.evals, self.x_f))

return x_f

def is_feasible(self, x):
Expand Down Expand Up @@ -182,32 +184,51 @@ def stopping_condition_iter(self):
self.next_iter()
return r

def return_conv(self):
r"""Get values of x and y axis for plotting covariance graph.
def convergence_data(self, x_axis='iters'):
r"""Get values of x and y-axis for plotting covariance graph.
Args:
x_axis (Literal['iters', 'evals']): Quantity to be displayed on the x-axis. Either 'iters' or 'evals'.
Returns:
Tuple[List[int], List[float]]:
1. List of ints of function evaluations.
2. List of ints of function/fitness values.
Tuple[np.ndarray, np.ndarray]:
1. array of function evaluations.
2. array of fitness values.
"""
if x_axis == 'iters':
return np.arange(self.iters), np.array(self.fitness_iters)
else: # x_axis == 'evals'
r1, r2 = [], []
for i, v in enumerate(self.n_evals):
r1.append(v)
r2.append(self.fitness_evals[i])
if i >= len(self.n_evals) - 1:
break
diff = self.n_evals[i + 1] - v
if diff <= 1:
continue
for j in range(diff - 1):
r1.append(v + j + 1)
r2.append(self.fitness_evals[i])
return np.array(r1), np.array(r2)

def plot_convergence(self, x_axis='iters', title='Convergence Graph'):
"""Plot a simple convergence graph.
Args:
x_axis (Literal['iters', 'evals']): Quantity to be displayed on the x-axis. Either 'iters' or 'evals'.
title (str): Title of the graph.
"""
r1, r2 = [], []
for i, v in enumerate(self.n_evals):
r1.append(v), r2.append(self.x_f_vals[i])
if i >= len(self.n_evals) - 1:
break
diff = self.n_evals[i + 1] - v
if diff <= 1:
continue
for j in range(diff - 1):
r1.append(v + j + 1), r2.append(self.x_f_vals[i])
return r1, r2

def plot(self):
"""Plot a simple convergence graph."""
fess, fitness = self.return_conv()
plt.plot(fess, fitness)
plt.xlabel('Function Evaluations')
x, fitness = self.convergence_data(x_axis)
_, ax = plt.subplots()
ax.plot(x, fitness)
ax.xaxis.set_major_locator(ticker.MaxNLocator(integer=True))
if x_axis == 'iters':
plt.xlabel('Iterations')
else:
plt.xlabel('Fitness Evaluations')
plt.ylabel('Fitness')
plt.title('Convergence Graph')
plt.title(title)
plt.show()
4 changes: 2 additions & 2 deletions niapy/tests/test_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ def test_print_conv_one(self):
for i in range(self.nFES):
x = np.full(self.D, 10 - i)
r1.append(i + 1), r2.append(self.task.eval(x))
t_r1, t_r2 = self.task.return_conv()
t_r1, t_r2 = self.task.convergence_data(x_axis='evals')
self.assertTrue(np.array_equal(r1, t_r1))
self.assertTrue(np.array_equal(r2, t_r2))

Expand All @@ -154,6 +154,6 @@ def test_print_conv_two(self):
for i in range(self.nFES):
x = np.full(self.D, 10 - i if i not in (3, 4, 5) else 4)
r1.append(i + 1), r2.append(self.task.eval(x))
t_r1, t_r2 = self.task.return_conv()
t_r1, t_r2 = self.task.convergence_data(x_axis='evals')
self.assertTrue(np.array_equal(r2, t_r2))
self.assertTrue(np.array_equal(r1, t_r1))
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ def build_description():
'astroid >= 2.0.4',
'pytest ~= 3.7.1',
'coverage ~= 4.4.2',
'coverage-space ~= 1.0.2'
'coverage-space ~= 1.0.2',
'bandit < 1.7.3'
],
install_requires=[
'numpy >= 1.17.0',
Expand Down

0 comments on commit 5e67e77

Please sign in to comment.