Skip to content

Commit

Permalink
Merge pull request anyoptimization#342 from bruscalia/feature/crowdin…
Browse files Browse the repository at this point in the history
…g_metrics

Feature new crowding metrics
  • Loading branch information
blankjul authored Jan 17, 2023
2 parents 16afc3b + 3c729f4 commit 2a7b101
Show file tree
Hide file tree
Showing 17 changed files with 1,729 additions and 110 deletions.
2 changes: 1 addition & 1 deletion MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
prune .
recursive-include pymoo *.py *.pyx
recursive-include pymoo *.py *.pyx *.pxd
recursive-include pymoo/cython/vendor *.cpp *.h
include LICENSE Makefile
54 changes: 54 additions & 0 deletions docs/source/operators/plots.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import matplotlib.pyplot as plt


def plot_pairs_3d(first, second, colors=("indigo", "firebrick"), **kwargs):

fig, ax = plt.subplots(1, 2, subplot_kw={'projection':'3d'}, **kwargs)

ax[0].scatter(
*first[1].T,
color=colors[0], label=first[0], marker="o",
)
ax[0].set_ylabel("$f_2$")
ax[0].set_xlabel("$f_1$")
ax[0].set_zlabel("$f_3$")
ax[0].legend()

ax[1].scatter(
*second[1].T,
color=colors[1], label=second[0], marker="o",
)
ax[1].set_ylabel("$f_2$")
ax[1].set_xlabel("$f_1$")
ax[1].set_zlabel("$f_3$")
ax[1].legend()

ax[0].view_init(elev=30, azim=30)
ax[1].view_init(elev=30, azim=30)

fig.tight_layout()
plt.show()


def plot_pairs_2d(first, second, colors=("indigo", "firebrick"), **kwargs):

fig, ax = plt.subplots(1, 2, **kwargs)

ax[0].scatter(
*first[1].T,
color=colors[0], label=first[0], marker="o",
)
ax[0].set_ylabel("$f_2$")
ax[0].set_xlabel("$f_1$")
ax[0].legend()

ax[1].scatter(
*second[1].T,
color=colors[1], label=second[0], marker="o",
)
ax[1].set_ylabel("$f_2$")
ax[1].set_xlabel("$f_1$")
ax[1].legend()

fig.tight_layout()
plt.show()
308 changes: 308 additions & 0 deletions docs/source/operators/survival.ipynb

Large diffs are not rendered by default.

50 changes: 44 additions & 6 deletions docs/source/references.bib
Original file line number Diff line number Diff line change
Expand Up @@ -164,11 +164,6 @@ @ARTICLE{nsga3-part2
}







@ARTICLE{moead,
author = {Qingfu Zhang and Hui Li},
title = {A multi-objective evolutionary algorithm based on decomposition},
Expand Down Expand Up @@ -715,4 +710,47 @@ @ARTICLE{isres
volume={35},
number={2},
pages={233-243},
doi={10.1109/TSMCC.2004.841906}}
doi={10.1109/TSMCC.2004.841906}}


@inproceedings{gde3,
title={GDE3: The third evolution step of generalized differential evolution},
author={Kukkonen, Saku and Lampinen, Jouni},
booktitle={2005 IEEE congress on evolutionary computation},
volume={1},
pages={443--450},
year={2005},
organization={IEEE}
}


@inproceedings{gde3pruning,
title={Improved pruning of non-dominated solutions based on crowding distance for bi-objective optimization problems},
author={Kukkonen, Saku and Deb, Kalyanmoy},
booktitle={2006 IEEE International Conference on Evolutionary Computation},
pages={1179--1186},
year={2006},
organization={IEEE}
}


@incollection{gde3many,
title={A fast and effective method for pruning of non-dominated solutions in many-objective problems},
author={Kukkonen, Saku and Deb, Kalyanmoy},
booktitle={Parallel problem solving from nature-PPSN IX},
pages={553--562},
year={2006},
publisher={Springer}
}


@article{mosade,
title={Multi-objective self-adaptive differential evolution with elitist archive and crowding entropy-based diversity measure},
author={Wang, Yao-Nan and Wu, Liang-Hong and Yuan, Xiao-Fang},
journal={Soft Computing},
volume={14},
number={3},
pages={193--209},
year={2010},
publisher={Springer}
}
110 changes: 13 additions & 97 deletions pymoo/algorithms/moo/nsga2.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import numpy as np
import warnings

from pymoo.algorithms.base.genetic import GeneticAlgorithm
from pymoo.core.survival import Survival
from pymoo.docs import parse_doc_string
from pymoo.operators.crossover.sbx import SBX
from pymoo.operators.mutation.pm import PM
from pymoo.operators.survival.rank_and_crowding import RankAndCrowding
from pymoo.operators.sampling.rnd import FloatRandomSampling
from pymoo.operators.selection.tournament import compare, TournamentSelection
from pymoo.termination.default import DefaultMultiObjectiveTermination
from pymoo.util.display.multi import MultiObjectiveOutput
from pymoo.util.dominator import Dominator
from pymoo.util.misc import find_duplicates, has_feasible
from pymoo.util.nds.non_dominated_sorting import NonDominatedSorting
from pymoo.util.randomized_argsort import randomized_argsort
from pymoo.util.misc import has_feasible


# ---------------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -68,47 +67,14 @@ def binary_tournament(pop, P, algorithm, **kwargs):
# ---------------------------------------------------------------------------------------------------------


class RankAndCrowdingSurvival(Survival):

def __init__(self, nds=None) -> None:
super().__init__(filter_infeasible=True)
self.nds = nds if nds is not None else NonDominatedSorting()

def _do(self, problem, pop, *args, n_survive=None, **kwargs):

# get the objective space values and objects
F = pop.get("F").astype(float, copy=False)

# the final indices of surviving individuals
survivors = []

# do the non-dominated sorting until splitting front
fronts = self.nds.do(F, n_stop_if_ranked=n_survive)

for k, front in enumerate(fronts):

# calculate the crowding distance of the front
crowding_of_front = calc_crowding_distance(F[front, :])

# save rank and crowding in the individual class
for j, i in enumerate(front):
pop[i].set("rank", k)
pop[i].set("crowding", crowding_of_front[j])

# current front sorted by crowding distance if splitting
if len(survivors) + len(front) > n_survive:
I = randomized_argsort(crowding_of_front, order='descending', method='numpy')
I = I[:(n_survive - len(survivors))]

# otherwise take the whole front unsorted
else:
I = np.arange(len(front))

# extend the survivors by all or selected individuals
survivors.extend(front[I])

return pop[survivors]

class RankAndCrowdingSurvival(RankAndCrowding):

def __init__(self, nds=None, crowding_func="cd"):
warnings.warn(
"RankAndCrowdingSurvival is deprecated and will be removed in version 0.8.*; use RankAndCrowding operator instead, which supports several and custom crowding diversity metrics.",
DeprecationWarning, 2
)
super().__init__(nds, crowding_func)

# =========================================================================================================
# Implementation
Expand All @@ -123,9 +89,10 @@ def __init__(self,
selection=TournamentSelection(func_comp=binary_tournament),
crossover=SBX(eta=15, prob=0.9),
mutation=PM(eta=20),
survival=RankAndCrowdingSurvival(),
survival=RankAndCrowding(),
output=MultiObjectiveOutput(),
**kwargs):

super().__init__(
pop_size=pop_size,
sampling=sampling,
Expand All @@ -147,55 +114,4 @@ def _set_optimum(self, **kwargs):
self.opt = self.pop[self.pop.get("rank") == 0]


def calc_crowding_distance(F, filter_out_duplicates=True):
n_points, n_obj = F.shape

if n_points <= 2:
return np.full(n_points, np.inf)

else:

if filter_out_duplicates:
# filter out solutions which are duplicates - duplicates get a zero finally
is_unique = np.where(np.logical_not(find_duplicates(F, epsilon=1e-32)))[0]
else:
# set every point to be unique without checking it
is_unique = np.arange(n_points)

# index the unique points of the array
_F = F[is_unique]

# sort each column and get index
I = np.argsort(_F, axis=0, kind='mergesort')

# sort the objective space values for the whole matrix
_F = _F[I, np.arange(n_obj)]

# calculate the distance from each point to the last and next
dist = np.row_stack([_F, np.full(n_obj, np.inf)]) - np.row_stack([np.full(n_obj, -np.inf), _F])

# calculate the norm for each objective - set to NaN if all values are equal
norm = np.max(_F, axis=0) - np.min(_F, axis=0)
norm[norm == 0] = np.nan

# prepare the distance to last and next vectors
dist_to_last, dist_to_next = dist, np.copy(dist)
dist_to_last, dist_to_next = dist_to_last[:-1] / norm, dist_to_next[1:] / norm

# if we divide by zero because all values in one columns are equal replace by none
dist_to_last[np.isnan(dist_to_last)] = 0.0
dist_to_next[np.isnan(dist_to_next)] = 0.0

# sum up the distance to next and last and norm by objectives - also reorder from sorted list
J = np.argsort(I, axis=0)
_cd = np.sum(dist_to_last[J, np.arange(n_obj)] + dist_to_next[J, np.arange(n_obj)], axis=1) / n_obj

# save the final vector which sets the crowding distance for duplicates to zero to be eliminated
crowding = np.zeros(n_points)
crowding[is_unique] = _cd

# crowding[np.isinf(crowding)] = 1e+14
return crowding


parse_doc_string(NSGA2.__init__)
Loading

0 comments on commit 2a7b101

Please sign in to comment.