Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature new DE algorithms #341

Open
wants to merge 35 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
4ff6134
Create new metrics for rank and crowding
mooscaliaproject Oct 17, 2022
98c3551
Create new DE based algorithms
mooscaliaproject Oct 17, 2022
8da25a8
Fix RankAndCrowding docs pcd
mooscaliaproject Oct 17, 2022
b3483e9
Merge branch 'feature/crowding_metrics' into feature/de_algorithms
mooscaliaproject Oct 17, 2022
0ab60bd
Included GDE3PCD as a class
mooscaliaproject Oct 17, 2022
91c7aad
Include tests for new crowding metrics
mooscaliaproject Nov 1, 2022
d850b3d
Fix constrained rank and crowding
mooscaliaproject Nov 1, 2022
97bf0ca
Fix crowding dist w duplicates test
mooscaliaproject Nov 1, 2022
2cd022c
Fix functional diversity n_points n_obj
mooscaliaproject Nov 1, 2022
5ceda2a
Fix mnn N versus M
mooscaliaproject Nov 1, 2022
f64dbd5
Include CowdingDiversity as a valid kwarg
mooscaliaproject Nov 1, 2022
0b0ef11
Merge branch 'develop' into feature/de_algorithms
mooscaliaproject Nov 1, 2022
69b92a9
Fix exp crossover for new DE
mooscaliaproject Nov 1, 2022
a0616bc
Include tests for soo DE
mooscaliaproject Nov 1, 2022
cae48e2
Fix test DE repair
mooscaliaproject Nov 1, 2022
cebc0b6
Include tests MODE
mooscaliaproject Nov 1, 2022
12737a0
Refactor DEX
mooscaliaproject Nov 1, 2022
cf99048
Fix new DEX
mooscaliaproject Nov 1, 2022
e5d2733
Revert DEX arguments
mooscaliaproject Nov 1, 2022
c57f478
Remove DEX from test_crossover
mooscaliaproject Nov 1, 2022
25eea6d
Fix DEX do
mooscaliaproject Nov 1, 2022
c8b1431
Refactor DEX and DEM to match SBX format
mooscaliaproject Nov 1, 2022
fbe12da
Fix DE de_repair on mating
mooscaliaproject Nov 1, 2022
edbaec9
Refactor infill of DE new class with inheritance
mooscaliaproject Nov 2, 2022
8fdd853
Fix DE algorithms with new mating
mooscaliaproject Nov 2, 2022
4646879
Fix kwargs passed to DE variant
mooscaliaproject Nov 2, 2022
884cb02
Style comments
mooscaliaproject Nov 2, 2022
f2c3d50
Fix eliminate duplicates None
mooscaliaproject Nov 2, 2022
c8d215c
Restore sampling LHS on NSDE
mooscaliaproject Nov 2, 2022
e5baab7
Style sampling of NSDER
mooscaliaproject Nov 2, 2022
977f758
Fix test DE GDE3 assertion
mooscaliaproject Nov 2, 2022
3536c23
Fix sampling on NSDER init
mooscaliaproject Nov 2, 2022
e73644d
Style code formatting with autopep8
mooscaliaproject Nov 18, 2022
c16481a
Merge branch 'feature/crowding_metrics' into feature/de_algorithms
mooscaliaproject Nov 18, 2022
3cb4f2b
Style code formatting with autopep8
mooscaliaproject Nov 18, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
148 changes: 148 additions & 0 deletions pymoo/algorithms/moo/gde3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
from pymoo.algorithms.moo.nsde import NSDE
from pymoo.core.population import Population
from pymoo.util.dominator import get_relation
from pymoo.operators.survival.rank_and_crowding import RankAndCrowding


# =========================================================================================================
# Implementation
# =========================================================================================================


class GDE3(NSDE):

def __init__(self,
pop_size=100,
variant="DE/rand/1/bin",
CR=0.5,
F=None,
gamma=1e-4,
**kwargs):
"""
GDE3 is an extension of DE to multi-objective problems using a mixed type survival strategy.
It is implemented in this version with the same constraint handling strategy of NSGA-II by default.

Derived algorithms GDE3-MNN and GDE3-2NN use by default survival RankAndCrowding with metrics 'mnn' and '2nn'.

For many-objective problems, try using NSDE-R, GDE3-MNN, or GDE3-2NN.

For Bi-objective problems, survival = RankAndCrowding(crowding_func='pcd') is very effective.

Kukkonen, S. & Lampinen, J., 2005. GDE3: The third evolution step of generalized differential evolution. 2005 IEEE congress on evolutionary computation, Volume 1, pp. 443-450.

Parameters
----------
pop_size : int, optional
Population size. Defaults to 100.

sampling : Sampling, optional
Sampling strategy of pymoo. Defaults to LHS().

variant : str, optional
Differential evolution strategy. Must be a string in the format: "DE/selection/n/crossover", in which, n in an integer of number of difference vectors, and crossover is either 'bin' or 'exp'. Selection variants are:

- 'ranked'
- 'rand'
- 'best'
- 'current-to-best'
- 'current-to-best'
- 'current-to-rand'
- 'rand-to-best'

The selection strategy 'ranked' might be helpful to improve convergence speed without much harm to diversity. Defaults to 'DE/rand/1/bin'.

CR : float, optional
Crossover parameter. Defined in the range [0, 1]
To reinforce mutation, use higher values. To control convergence speed, use lower values.

F : iterable of float or float, optional
Scale factor or mutation parameter. Defined in the range (0, 2]
To reinforce exploration, use higher values; for exploitation, use lower values.

gamma : float, optional
Jitter deviation parameter. Should be in the range (0, 2). Defaults to 1e-4.

de_repair : str, optional
Repair of DE mutant vectors. Is either callable or one of:

- 'bounce-back'
- 'midway'
- 'rand-init'
- 'to-bounds'

If callable, has the form fun(X, Xb, xl, xu) in which X contains mutated vectors including violations, Xb contains reference vectors for repair in feasible space, xl is a 1d vector of lower bounds, and xu a 1d vector of upper bounds.
Defaults to 'bounce-back'.

mutation : Mutation, optional
Pymoo's mutation operator after crossover. Defaults to NoMutation().

repair : Repair, optional
Pymoo's repair operator after mutation. Defaults to NoRepair().

survival : Survival, optional
Pymoo's survival strategy.
Defaults to RankAndCrowding() with crowding distances ('cd').
In GDE3, the survival strategy is applied after a one-to-one comparison between child vector and corresponding parent when both are non-dominated by the other.
"""

super().__init__(pop_size=pop_size,
variant=variant,
CR=CR,
F=F,
gamma=gamma,
**kwargs)

def _advance(self, infills=None, **kwargs):

assert infills is not None, "This algorithms uses the AskAndTell interface thus 'infills' must to be provided."

# The individuals that are considered for the survival later and final survive
survivors = []

# now for each of the infill solutions
for k in range(len(self.pop)):

# Get the offspring an the parent it is coming from
off, parent = infills[k], self.pop[k]

# Check whether the new solution dominates the parent or not
rel = get_relation(parent, off)

# If indifferent we add both
if rel == 0:
survivors.extend([parent, off])

# If offspring dominates parent
elif rel == -1:
survivors.append(off)

# If parent dominates offspring
else:
survivors.append(parent)

# Create the population
survivors = Population.create(*survivors)

# Perform a survival to reduce to pop size
self.pop = self.survival.do(self.problem, survivors, n_survive=self.n_offsprings)


class GDE3MNN(GDE3):

def __init__(self, pop_size=100, variant="DE/rand/1/bin", CR=0.5, F=None, gamma=0.0001, **kwargs):
survival = RankAndCrowding(crowding_func="mnn")
super().__init__(pop_size, variant, CR, F, gamma, survival=survival, **kwargs)


class GDE32NN(GDE3):

def __init__(self, pop_size=100, variant="DE/rand/1/bin", CR=0.5, F=None, gamma=0.0001, **kwargs):
survival = RankAndCrowding(crowding_func="2nn")
super().__init__(pop_size, variant, CR, F, gamma, survival=survival, **kwargs)


class GDE3PCD(GDE3):

def __init__(self, pop_size=100, variant="DE/rand/1/bin", CR=0.5, F=None, gamma=0.0001, **kwargs):
survival = RankAndCrowding(crowding_func="pcd")
super().__init__(pop_size, variant, CR, F, gamma, survival=survival, **kwargs)
105 changes: 105 additions & 0 deletions pymoo/algorithms/moo/nsde.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
from pymoo.algorithms.moo.nsga2 import NSGA2
from pymoo.operators.sampling.lhs import LHS
from pymoo.algorithms.soo.nonconvex.de import VariantDE
from pymoo.operators.survival.rank_and_crowding import RankAndCrowding


# =========================================================================================================
# Implementation
# =========================================================================================================


class NSDE(NSGA2):

def __init__(self,
pop_size=100,
sampling=LHS(),
variant="DE/rand/1/bin",
CR=0.7,
F=None,
gamma=1e-4,
de_repair="bounce-back",
survival=RankAndCrowding(),
**kwargs):
"""
NSDE is an algorithm that combines that combines NSGA-II sorting and survival strategies
to DE mutation and crossover.

For many-objective problems, try using NSDE-R, GDE3-MNN, or GDE3-2NN.

For Bi-objective problems, survival = RankAndCrowding(crowding_func='pcd') is very effective.

Parameters
----------
pop_size : int, optional
Population size. Defaults to 100.

sampling : Sampling, optional
Sampling strategy of pymoo. Defaults to LHS().

variant : str, optional
Differential evolution strategy. Must be a string in the format: "DE/selection/n/crossover", in which, n in an integer of number of difference vectors, and crossover is either 'bin' or 'exp'. Selection variants are:

- "ranked'
- 'rand'
- 'best'
- 'current-to-best'
- 'current-to-best'
- 'current-to-rand'
- 'rand-to-best'

The selection strategy 'ranked' might be helpful to improve convergence speed without much harm to diversity. Defaults to 'DE/rand/1/bin'.

CR : float, optional
Crossover parameter. Defined in the range [0, 1]
To reinforce mutation, use higher values. To control convergence speed, use lower values.

F : iterable of float or float, optional
Scale factor or mutation parameter. Defined in the range (0, 2]
To reinforce exploration, use higher values; for exploitation, use lower values.

gamma : float, optional
Jitter deviation parameter. Should be in the range (0, 2). Defaults to 1e-4.

de_repair : str, optional
Repair of DE mutant vectors. Is either callable or one of:

- 'bounce-back'
- 'midway'
- 'rand-init'
- 'to-bounds'

If callable, has the form fun(X, Xb, xl, xu) in which X contains mutated vectors including violations, Xb contains reference vectors for repair in feasible space, xl is a 1d vector of lower bounds, and xu a 1d vector of upper bounds.
Defaults to 'bounce-back'.

mutation : Mutation, optional
Pymoo's mutation operator after crossover. Defaults to NoMutation().

repair : Repair, optional
Pymoo's repair operator after mutation. Defaults to NoRepair().

survival : Survival, optional
Pymoo's survival strategy.
Defaults to RankAndCrowding() with crowding distances ('cd').
In GDE3, the survival strategy is applied after a one-to-one comparison between child vector and corresponding parent when both are non-dominated by the other.
"""

# Number of offsprings at each generation
n_offsprings = pop_size

# Mating
mating = VariantDE(variant=variant,
CR=CR,
F=F,
gamma=gamma,
de_repair=de_repair,
**kwargs)

# Init from pymoo's NSGA2
super().__init__(pop_size=pop_size,
sampling=sampling,
mating=mating,
survival=survival,
eliminate_duplicates=None,
n_offsprings=n_offsprings,
**kwargs)
123 changes: 123 additions & 0 deletions pymoo/algorithms/moo/nsder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import numpy as np
from pymoo.algorithms.moo.nsga3 import ReferenceDirectionSurvival
from pymoo.operators.sampling.lhs import LHS
from pymoo.util.misc import has_feasible
from pymoo.algorithms.moo.nsde import NSDE

# =========================================================================================================
# Implementation
# =========================================================================================================


class NSDER(NSDE):

def __init__(self,
ref_dirs,
pop_size=100,
variant="DE/rand/1/bin",
CR=0.7,
F=None,
gamma=1e-4,
**kwargs):
"""
NSDE-R is an extension of NSDE to many-objective problems (Reddy & Dulikravich, 2019) using NSGA-III survival.

S. R. Reddy and G. S. Dulikravich, "Many-objective differential evolution optimization based on reference points: NSDE-R," Struct. Multidisc. Optim., vol. 60, pp. 1455-1473, 2019.

Parameters
----------
ref_dirs : array like
The reference directions that should be used during the optimization.

pop_size : int, optional
Population size. Defaults to 100.

sampling : Sampling, optional
Sampling strategy of pymoo. Defaults to LHS().

variant : str, optional
Differential evolution strategy. Must be a string in the format: "DE/selection/n/crossover", in which, n in an integer of number of difference vectors, and crossover is either 'bin' or 'exp'. Selection variants are:

- "ranked'
- 'rand'
- 'best'
- 'current-to-best'
- 'current-to-best'
- 'current-to-rand'
- 'rand-to-best'

The selection strategy 'ranked' might be helpful to improve convergence speed without much harm to diversity. Defaults to 'DE/rand/1/bin'.

CR : float, optional
Crossover parameter. Defined in the range [0, 1]
To reinforce mutation, use higher values. To control convergence speed, use lower values.

F : iterable of float or float, optional
Scale factor or mutation parameter. Defined in the range (0, 2]
To reinforce exploration, use higher values; for exploitation, use lower values.

gamma : float, optional
Jitter deviation parameter. Should be in the range (0, 2). Defaults to 1e-4.

de_repair : str, optional
Repair of DE mutant vectors. Is either callable or one of:

- 'bounce-back'
- 'midway'
- 'rand-init'
- 'to-bounds'

If callable, has the form fun(X, Xb, xl, xu) in which X contains mutated vectors including violations, Xb contains reference vectors for repair in feasible space, xl is a 1d vector of lower bounds, and xu a 1d vector of upper bounds.
Defaults to 'bounce-back'.

mutation : Mutation, optional
Pymoo's mutation operator after crossover. Defaults to NoMutation().

repair : Repair, optional
Pymoo's repair operator after mutation. Defaults to NoRepair().

survival : Survival, optional
Pymoo's survival strategy.
Defaults to ReferenceDirectionSurvival().
"""

self.ref_dirs = ref_dirs

if self.ref_dirs is not None:

if pop_size is None:
pop_size = len(self.ref_dirs)

if pop_size < len(self.ref_dirs):
print(
f"WARNING: pop_size={pop_size} is less than the number of reference directions ref_dirs={len(self.ref_dirs)}.\n"
"This might cause unwanted behavior of the algorithm. \n"
"Please make sure pop_size is equal or larger than the number of reference directions. ")

if 'survival' in kwargs:
survival = kwargs['survival']
del kwargs['survival']
else:
survival = ReferenceDirectionSurvival(ref_dirs)

super().__init__(pop_size=pop_size,
variant=variant,
CR=CR,
F=F,
gamma=gamma,
survival=survival,
**kwargs)

def _setup(self, problem, **kwargs):

if self.ref_dirs is not None:
if self.ref_dirs.shape[1] != problem.n_obj:
raise Exception(
"Dimensionality of reference points must be equal to the number of objectives: %s != %s" %
(self.ref_dirs.shape[1], problem.n_obj))

def _set_optimum(self, **kwargs):
if not has_feasible(self.pop):
self.opt = self.pop[[np.argmin(self.pop.get("CV"))]]
else:
self.opt = self.survival.opt
Loading