From 98c3551f4826767971d6f363750dcd532a7f5a04 Mon Sep 17 00:00:00 2001 From: mooscalia project <93492480+mooscaliaproject@users.noreply.github.com> Date: Mon, 17 Oct 2022 12:46:57 -0300 Subject: [PATCH 01/23] Create new DE based algorithms --- pymoo/algorithms/moo/gde3.py | 141 +++++++++ pymoo/algorithms/moo/nsde.py | 108 +++++++ pymoo/algorithms/moo/nsder.py | 124 ++++++++ pymoo/algorithms/soo/nonconvex/de.py | 388 +++++++++--------------- pymoo/algorithms/soo/nonconvex/de_ep.py | 279 +++++++++++++++++ pymoo/algorithms/soo/nonconvex/pso.py | 3 +- pymoo/operators/crossover/dex.py | 388 ++++++++++++++++++------ pymoo/operators/selection/des.py | 213 +++++++++++++ 8 files changed, 1304 insertions(+), 340 deletions(-) create mode 100644 pymoo/algorithms/moo/gde3.py create mode 100644 pymoo/algorithms/moo/nsde.py create mode 100644 pymoo/algorithms/moo/nsder.py create mode 100644 pymoo/algorithms/soo/nonconvex/de_ep.py create mode 100644 pymoo/operators/selection/des.py diff --git a/pymoo/algorithms/moo/gde3.py b/pymoo/algorithms/moo/gde3.py new file mode 100644 index 000000000..588822cd2 --- /dev/null +++ b/pymoo/algorithms/moo/gde3.py @@ -0,0 +1,141 @@ +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) \ No newline at end of file diff --git a/pymoo/algorithms/moo/nsde.py b/pymoo/algorithms/moo/nsde.py new file mode 100644 index 000000000..e730350e6 --- /dev/null +++ b/pymoo/algorithms/moo/nsde.py @@ -0,0 +1,108 @@ +from pymoo.algorithms.moo.nsga2 import NSGA2 +from pymoo.operators.sampling.lhs import LHS +from pymoo.algorithms.soo.nonconvex.de import InfillDE +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", + mutation=None, + repair=None, + 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 = InfillDE(variant=variant, + CR=CR, + F=F, + gamma=gamma, + de_repair=de_repair, + mutation=mutation, + repair=repair) + + #Init from pymoo's NSGA2 + super().__init__(pop_size=pop_size, + sampling=sampling, + mating=mating, + survival=survival, + eliminate_duplicates=False, + n_offsprings=n_offsprings, + **kwargs) diff --git a/pymoo/algorithms/moo/nsder.py b/pymoo/algorithms/moo/nsder.py new file mode 100644 index 000000000..e5e0349e1 --- /dev/null +++ b/pymoo/algorithms/moo/nsder.py @@ -0,0 +1,124 @@ +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, + sampling=LHS(), + 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, + sampling=sampling, + 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 diff --git a/pymoo/algorithms/soo/nonconvex/de.py b/pymoo/algorithms/soo/nonconvex/de.py index 3a5562f51..a01c39fc3 100755 --- a/pymoo/algorithms/soo/nonconvex/de.py +++ b/pymoo/algorithms/soo/nonconvex/de.py @@ -1,279 +1,183 @@ -""" - -Differential Evolution (DE) - --------------------------------- Description ------------------------------- - - - --------------------------------- References -------------------------------- - -[1] J. Blank and K. Deb, pymoo: Multi-Objective Optimization in Python, in IEEE Access, -vol. 8, pp. 89497-89509, 2020, DOI: 10.1109/ACCESS.2020.2990567 - --------------------------------- License ----------------------------------- - - ----------------------------------------------------------------------------- -""" - import numpy as np - from pymoo.algorithms.base.genetic import GeneticAlgorithm from pymoo.algorithms.soo.nonconvex.ga import FitnessSurvival -from pymoo.core.infill import InfillCriterion -from pymoo.core.population import Population from pymoo.core.replacement import ImprovementReplacement -from pymoo.core.variable import Choice, get -from pymoo.core.variable import Real -from pymoo.docs import parse_doc_string -from pymoo.operators.control import EvolutionaryParameterControl, NoParameterControl -from pymoo.operators.crossover.binx import mut_binomial -from pymoo.operators.crossover.expx import mut_exp -from pymoo.operators.mutation.pm import PM -from pymoo.operators.repair.bounds_repair import repair_random_init -from pymoo.operators.sampling.rnd import FloatRandomSampling -from pymoo.operators.selection.rnd import fast_fill_random +from pymoo.operators.mutation.nom import NoMutation +from pymoo.core.repair import NoRepair +from pymoo.operators.sampling.lhs import LHS from pymoo.termination.default import DefaultSingleObjectiveTermination from pymoo.util.display.single import SingleObjectiveOutput -from pymoo.util.misc import where_is_what +from pymoo.operators.selection.des import DES +from pymoo.operators.crossover.dex import DEX # ========================================================================================================= -# Crossover -# ========================================================================================================= - -def de_differential(X, F, jitter, alpha=0.001): - n_parents, n_matings, n_var = X.shape - assert n_parents % 2 == 1, "For the differential an odd number of values need to be provided" - - # the differentials from each pair - delta = np.zeros((n_matings, n_var)) - - # for each difference of the differences - for i in range(1, n_parents, 2): - # create the weight vectors with jitter to give some variation - _F = F[:, None].repeat(n_var, axis=1) - _F[jitter] *= (1 + alpha * (np.random.random((jitter.sum(), n_var)) - 0.5)) - - # add the difference to the vector - delta += _F * (X[i] - X[i + 1]) - - # now add the differentials to the first parent - Xp = X[0] + delta - - return Xp - - -# ========================================================================================================= -# Variant +# Implementation # ========================================================================================================= -class Variant(InfillCriterion): - +class InfillDE: + def __init__(self, - selection="best", - n_diffs=1, - F=0.5, - crossover="bin", - CR=0.2, - jitter=False, - prob_mut=0.1, - control=EvolutionaryParameterControl, - **kwargs): - - super().__init__(**kwargs) - self.selection = Choice(selection, options=["rand", "best"], all=["rand", "best", "target-to-best"]) - self.n_diffs = Choice(n_diffs, options=[1], all=[1, 2]) - self.F = Real(F, bounds=(0.4, 0.7), strict=(0.0, None)) - self.crossover = Choice(crossover, ["bin"], all=["bin", "exp", "hypercube", "line"]) - self.CR = Real(CR, bounds=(0.2, 0.8), strict=(0.0, 1.0)) - self.jitter = Choice(jitter, options=[False], all=[True, False]) - - self.mutation = PM(at_least_once=True) - self.mutation.eta = 20 - self.mutation.prob = prob_mut - - self.control = control(self) - - def do(self, problem, pop, n_offsprings, algorithm=None, **kwargs): - control = self.control - - # let the parameter control now some information - control.tell(pop=pop) - - # set the controlled parameter for the desired number of offsprings - control.do(n_offsprings) - - # find the different groups of selection schemes and order them by category - sel, n_diffs = get(self.selection, self.n_diffs, size=n_offsprings) - H = where_is_what(zip(sel, n_diffs)) - - # get the parameters used for reproduction during the crossover - F, CR, jitter = get(self.F, self.CR, self.jitter, size=n_offsprings) - - # the `target` vectors which will be recombined - X = pop.get("X") - - # the `donor` vector which will be obtained through the differential equation - donor = np.full((n_offsprings, problem.n_var), np.nan) - - # for each type defined by the type and number of differentials - for (sel_type, n_diffs), targets in H.items(): - - # the number of offsprings created in this run - n_matings, n_parents = len(targets), 1 + 2 * n_diffs - - # create the parents array - P = np.full([n_matings, n_parents], -1) - - itself = np.array(targets)[:, None] - - best = lambda: np.random.choice(np.where(pop.get("rank") == 0)[0], replace=True, size=n_matings) - - if sel_type == "rand": - fast_fill_random(P, len(pop), columns=range(n_parents), Xp=itself) - elif sel_type == "best": - P[:, 0] = best() - fast_fill_random(P, len(pop), columns=range(1, n_parents), Xp=itself) - elif sel_type == "target-to-best": - P[:, 0] = targets - P[:, 1] = best() - fast_fill_random(P, len(pop), columns=range(2, n_parents), Xp=itself) - else: - raise Exception("Unknown selection method.") - - # get the values of the parents in the design space - XX = np.swapaxes(X[P], 0, 1) - - # do the differential crossover to create the donor vector - Xp = de_differential(XX, F[targets], jitter[targets]) - - # make sure everything stays in bounds - if problem.has_bounds(): - Xp = repair_random_init(Xp, XX[0], *problem.bounds()) - - # set the donors (the one we have created in this step) - donor[targets] = Xp - - # the `trial` created by by recombining target and donor - trial = np.full((n_offsprings, problem.n_var), np.nan) - - crossover = get(self.crossover, size=n_offsprings) - for name, K in where_is_what(crossover).items(): - - _target = X[K] - _donor = donor[K] - _CR = CR[K] - - if name == "bin": - M = mut_binomial(len(K), problem.n_var, _CR, at_least_once=True) - _trial = np.copy(_target) - _trial[M] = _donor[M] - elif name == "exp": - M = mut_exp(n_offsprings, problem.n_var, _CR, at_least_once=True) - _trial = np.copy(_target) - _trial[M] = _donor[M] - elif name == "line": - w = np.random.random((len(K), 1)) * _CR[:, None] - _trial = _target + w * (_donor - _target) - elif name == "hypercube": - w = np.random.random((len(K), _target.shape[1])) * _CR[:, None] - _trial = _target + w * (_donor - _target) - else: - raise Exception(f"Unknown crossover variant: {name}") - - trial[K] = _trial - - # create the population - off = Population.new(X=trial) - - # do the mutation which helps to add some more diversity - off = self.mutation(problem, off) - - # repair the individuals if necessary - disabled if repair is NoRepair - off = self.repair(problem, off, **kwargs) - - # advance the parameter control by attaching them to the offsprings - control.advance(off) - + variant="DE/rand/1/bin", + CR=0.7, + F=(0.5, 1.0), + gamma=1e-4, + de_repair="bounce-back", + mutation=None, + repair=None): + + # Parse the information from the string + _, selection_variant, n_diff, crossover_variant, = variant.split("/") + n_diffs = int(n_diff) + + # When "to" in variant there are more than 1 difference vectors + if "-to-" in variant: + n_diffs += 1 + + # Define parent selection operator + self.selection = DES(selection_variant) + + #Default value for F + if F is None: + F = (0.0, 1.0) + + # Define crossover strategy + self.crossover = DEX(variant=crossover_variant, + CR=CR, + F=F, + gamma=gamma, + n_diffs=n_diffs, + at_least_once=True, + repair=repair) + + # Define posterior mutation strategy and repair + self.mutation = mutation if mutation is not None else NoMutation() + self.repair = repair if repair is not None else NoRepair() + + def do(self, problem, pop, n_offsprings, **kwargs): + + # Select parents including donor vector + parents = self.selection.do(problem, pop, n_offsprings, self.crossover.n_parents, + to_pop=False, **kwargs) + + # Perform mutation included in DEX and crossover + off = self.crossover.do(problem, pop, parents, **kwargs) + + # Perform posterior mutation and repair if passed + off = self.mutation.do(problem, off) + off = self.repair.do(problem, off) + return off - - -# ========================================================================================================= -# Implementation -# ========================================================================================================= - + class DE(GeneticAlgorithm): def __init__(self, pop_size=100, - n_offsprings=None, - sampling=FloatRandomSampling(), - variant="DE/best/1/bin", + sampling=LHS(), + variant="DE/rand/1/bin", + CR=0.7, + F=(0.5, 1.0), + gamma=1e-4, + de_repair="bounce-back", + mutation=None, + repair=None, output=SingleObjectiveOutput(), - **kwargs - ): - - if variant is None: - if "control" not in kwargs: - kwargs["control"] = NoParameterControl - variant = Variant(**kwargs) - - elif isinstance(variant, str): - try: - _, selection, n_diffs, crossover = variant.split("/") - if "control" not in kwargs: - kwargs["control"] = NoParameterControl - variant = Variant(selection=selection, n_diffs=int(n_diffs), crossover=crossover, **kwargs) - except: - raise Exception("Please provide a valid variant: DE///") + **kwargs): + """ + Single-objective Differential Evolution proposed by Storn and Price (1997). + + Storn, R. & Price, K., 1997. Differential evolution–a simple and efficient heuristic for global optimization over continuous spaces. J. Glob. Optim., 11(4), pp. 341-359. + + 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(). + """ + + mating = InfillDE(variant=variant, + CR=CR, + F=F, + gamma=gamma, + de_repair=de_repair, + mutation=mutation, + repair=repair) + + # Number of offsprings at each generation + n_offsprings = pop_size super().__init__(pop_size=pop_size, - n_offsprings=n_offsprings, sampling=sampling, - mating=variant, - survival=None, - output=output, + mating=mating, + n_offsprings=n_offsprings, eliminate_duplicates=False, + output=output, **kwargs) self.termination = DefaultSingleObjectiveTermination() def _initialize_advance(self, infills=None, **kwargs): - FitnessSurvival().do(self.problem, self.pop, return_indices=True) + self.pop = FitnessSurvival().do(self.problem, infills, n_survive=self.pop_size) def _infill(self): + infills = self.mating.do(self.problem, self.pop, self.n_offsprings, algorithm=self) - # tag each individual with an index - if a steady state version is executed - index = np.arange(len(infills)) - - # if number of offsprings is set lower than pop_size - randomly select - if self.n_offsprings < self.pop_size: - index = np.random.permutation(len(infills))[:self.n_offsprings] - infills = infills[index] - - infills.set("index", index) - return infills def _advance(self, infills=None, **kwargs): - assert infills is not None, "This algorithms uses the AskAndTell interface thus infills must to be provided." - - # get the indices where each offspring is originating from - I = infills.get("index") - - # replace the individuals with the corresponding parents from the mating - self.pop[I] = ImprovementReplacement().do(self.problem, self.pop[I], infills) - - # update the information regarding the current population - FitnessSurvival().do(self.problem, self.pop, return_indices=True) - - def _set_optimum(self, **kwargs): - k = self.pop.get("rank") == 0 - self.opt = self.pop[k] + + assert infills is not None, "This algorithms uses the AskAndTell interface thus infills must be provided." + #One-to-one replacement survival + self.pop = ImprovementReplacement().do(self.problem, self.pop, infills) -parse_doc_string(DE.__init__) + #Sort the population by fitness to make the selection simpler for mating (not an actual survival, just sorting) + self.pop = FitnessSurvival().do(self.problem, self.pop) + + #Set ranks + self.pop.set("rank", np.arange(self.pop_size)) diff --git a/pymoo/algorithms/soo/nonconvex/de_ep.py b/pymoo/algorithms/soo/nonconvex/de_ep.py new file mode 100644 index 000000000..690a36d86 --- /dev/null +++ b/pymoo/algorithms/soo/nonconvex/de_ep.py @@ -0,0 +1,279 @@ +""" + +Differential Evolution (DE) + +-------------------------------- Description ------------------------------- + + + +-------------------------------- References -------------------------------- + +[1] J. Blank and K. Deb, pymoo: Multi-Objective Optimization in Python, in IEEE Access, +vol. 8, pp. 89497-89509, 2020, DOI: 10.1109/ACCESS.2020.2990567 + +-------------------------------- License ----------------------------------- + + +---------------------------------------------------------------------------- +""" + +import numpy as np + +from pymoo.algorithms.base.genetic import GeneticAlgorithm +from pymoo.algorithms.soo.nonconvex.ga import FitnessSurvival +from pymoo.core.infill import InfillCriterion +from pymoo.core.population import Population +from pymoo.core.replacement import ImprovementReplacement +from pymoo.core.variable import Choice, get +from pymoo.core.variable import Real +from pymoo.docs import parse_doc_string +from pymoo.operators.control import EvolutionaryParameterControl, NoParameterControl +from pymoo.operators.crossover.binx import mut_binomial +from pymoo.operators.crossover.expx import mut_exp +from pymoo.operators.mutation.pm import PM +from pymoo.operators.repair.bounds_repair import repair_random_init +from pymoo.operators.sampling.rnd import FloatRandomSampling +from pymoo.operators.selection.rnd import fast_fill_random +from pymoo.termination.default import DefaultSingleObjectiveTermination +from pymoo.util.display.single import SingleObjectiveOutput +from pymoo.util.misc import where_is_what + + +# ========================================================================================================= +# Crossover +# ========================================================================================================= + +def de_differential(X, F, jitter, alpha=0.001): + n_parents, n_matings, n_var = X.shape + assert n_parents % 2 == 1, "For the differential an odd number of values need to be provided" + + # the differentials from each pair + delta = np.zeros((n_matings, n_var)) + + # for each difference of the differences + for i in range(1, n_parents, 2): + # create the weight vectors with jitter to give some variation + _F = F[:, None].repeat(n_var, axis=1) + _F[jitter] *= (1 + alpha * (np.random.random((jitter.sum(), n_var)) - 0.5)) + + # add the difference to the vector + delta += _F * (X[i] - X[i + 1]) + + # now add the differentials to the first parent + Xp = X[0] + delta + + return Xp + + +# ========================================================================================================= +# Variant +# ========================================================================================================= + +class Variant(InfillCriterion): + + def __init__(self, + selection="best", + n_diffs=1, + F=0.5, + crossover="bin", + CR=0.2, + jitter=False, + prob_mut=0.1, + control=EvolutionaryParameterControl, + **kwargs): + + super().__init__(**kwargs) + self.selection = Choice(selection, options=["rand", "best"], all=["rand", "best", "target-to-best"]) + self.n_diffs = Choice(n_diffs, options=[1], all=[1, 2]) + self.F = Real(F, bounds=(0.4, 0.7), strict=(0.0, None)) + self.crossover = Choice(crossover, ["bin"], all=["bin", "exp", "hypercube", "line"]) + self.CR = Real(CR, bounds=(0.2, 0.8), strict=(0.0, 1.0)) + self.jitter = Choice(jitter, options=[False], all=[True, False]) + + self.mutation = PM(at_least_once=True) + self.mutation.eta = 20 + self.mutation.prob = prob_mut + + self.control = control(self) + + def do(self, problem, pop, n_offsprings, algorithm=None, **kwargs): + control = self.control + + # let the parameter control now some information + control.tell(pop=pop) + + # set the controlled parameter for the desired number of offsprings + control.do(n_offsprings) + + # find the different groups of selection schemes and order them by category + sel, n_diffs = get(self.selection, self.n_diffs, size=n_offsprings) + H = where_is_what(zip(sel, n_diffs)) + + # get the parameters used for reproduction during the crossover + F, CR, jitter = get(self.F, self.CR, self.jitter, size=n_offsprings) + + # the `target` vectors which will be recombined + X = pop.get("X") + + # the `donor` vector which will be obtained through the differential equation + donor = np.full((n_offsprings, problem.n_var), np.nan) + + # for each type defined by the type and number of differentials + for (sel_type, n_diffs), targets in H.items(): + + # the number of offsprings created in this run + n_matings, n_parents = len(targets), 1 + 2 * n_diffs + + # create the parents array + P = np.full([n_matings, n_parents], -1) + + itself = np.array(targets)[:, None] + + best = lambda: np.random.choice(np.where(pop.get("rank") == 0)[0], replace=True, size=n_matings) + + if sel_type == "rand": + fast_fill_random(P, len(pop), columns=range(n_parents), Xp=itself) + elif sel_type == "best": + P[:, 0] = best() + fast_fill_random(P, len(pop), columns=range(1, n_parents), Xp=itself) + elif sel_type == "target-to-best": + P[:, 0] = targets + P[:, 1] = best() + fast_fill_random(P, len(pop), columns=range(2, n_parents), Xp=itself) + else: + raise Exception("Unknown selection method.") + + # get the values of the parents in the design space + XX = np.swapaxes(X[P], 0, 1) + + # do the differential crossover to create the donor vector + Xp = de_differential(XX, F[targets], jitter[targets]) + + # make sure everything stays in bounds + if problem.has_bounds(): + Xp = repair_random_init(Xp, XX[0], *problem.bounds()) + + # set the donors (the one we have created in this step) + donor[targets] = Xp + + # the `trial` created by by recombining target and donor + trial = np.full((n_offsprings, problem.n_var), np.nan) + + crossover = get(self.crossover, size=n_offsprings) + for name, K in where_is_what(crossover).items(): + + _target = X[K] + _donor = donor[K] + _CR = CR[K] + + if name == "bin": + M = mut_binomial(len(K), problem.n_var, _CR, at_least_once=True) + _trial = np.copy(_target) + _trial[M] = _donor[M] + elif name == "exp": + M = mut_exp(n_offsprings, problem.n_var, _CR, at_least_once=True) + _trial = np.copy(_target) + _trial[M] = _donor[M] + elif name == "line": + w = np.random.random((len(K), 1)) * _CR[:, None] + _trial = _target + w * (_donor - _target) + elif name == "hypercube": + w = np.random.random((len(K), _target.shape[1])) * _CR[:, None] + _trial = _target + w * (_donor - _target) + else: + raise Exception(f"Unknown crossover variant: {name}") + + trial[K] = _trial + + # create the population + off = Population.new(X=trial) + + # do the mutation which helps to add some more diversity + off = self.mutation(problem, off) + + # repair the individuals if necessary - disabled if repair is NoRepair + off = self.repair(problem, off, **kwargs) + + # advance the parameter control by attaching them to the offsprings + control.advance(off) + + return off + + +# ========================================================================================================= +# Implementation +# ========================================================================================================= + + +class EPDE(GeneticAlgorithm): + + def __init__(self, + pop_size=100, + n_offsprings=None, + sampling=FloatRandomSampling(), + variant="DE/best/1/bin", + output=SingleObjectiveOutput(), + **kwargs + ): + + if variant is None: + if "control" not in kwargs: + kwargs["control"] = NoParameterControl + variant = Variant(**kwargs) + + elif isinstance(variant, str): + try: + _, selection, n_diffs, crossover = variant.split("/") + if "control" not in kwargs: + kwargs["control"] = NoParameterControl + variant = Variant(selection=selection, n_diffs=int(n_diffs), crossover=crossover, **kwargs) + except: + raise Exception("Please provide a valid variant: DE///") + + super().__init__(pop_size=pop_size, + n_offsprings=n_offsprings, + sampling=sampling, + mating=variant, + survival=None, + output=output, + eliminate_duplicates=False, + **kwargs) + + self.termination = DefaultSingleObjectiveTermination() + + def _initialize_advance(self, infills=None, **kwargs): + FitnessSurvival().do(self.problem, self.pop, return_indices=True) + + def _infill(self): + infills = self.mating.do(self.problem, self.pop, self.n_offsprings, algorithm=self) + + # tag each individual with an index - if a steady state version is executed + index = np.arange(len(infills)) + + # if number of offsprings is set lower than pop_size - randomly select + if self.n_offsprings < self.pop_size: + index = np.random.permutation(len(infills))[:self.n_offsprings] + infills = infills[index] + + infills.set("index", index) + + return infills + + def _advance(self, infills=None, **kwargs): + assert infills is not None, "This algorithms uses the AskAndTell interface thus infills must to be provided." + + # get the indices where each offspring is originating from + I = infills.get("index") + + # replace the individuals with the corresponding parents from the mating + self.pop[I] = ImprovementReplacement().do(self.problem, self.pop[I], infills) + + # update the information regarding the current population + FitnessSurvival().do(self.problem, self.pop, return_indices=True) + + def _set_optimum(self, **kwargs): + k = self.pop.get("rank") == 0 + self.opt = self.pop[k] + + +parse_doc_string(EPDE.__init__) diff --git a/pymoo/algorithms/soo/nonconvex/pso.py b/pymoo/algorithms/soo/nonconvex/pso.py index 5943eae1b..6973f67e6 100644 --- a/pymoo/algorithms/soo/nonconvex/pso.py +++ b/pymoo/algorithms/soo/nonconvex/pso.py @@ -8,9 +8,8 @@ from pymoo.core.repair import NoRepair from pymoo.core.replacement import ImprovementReplacement from pymoo.docs import parse_doc_string -from pymoo.operators.crossover.dex import repair_random_init from pymoo.operators.mutation.pm import PM -from pymoo.operators.repair.bounds_repair import is_out_of_bounds_by_problem +from pymoo.operators.repair.bounds_repair import is_out_of_bounds_by_problem, repair_random_init from pymoo.operators.repair.to_bound import set_to_bounds_if_outside from pymoo.operators.sampling.lhs import LHS from pymoo.util.display.column import Column diff --git a/pymoo/operators/crossover/dex.py b/pymoo/operators/crossover/dex.py index 157856498..3e2d5135f 100644 --- a/pymoo/operators/crossover/dex.py +++ b/pymoo/operators/crossover/dex.py @@ -1,122 +1,318 @@ import numpy as np - from pymoo.core.crossover import Crossover from pymoo.core.population import Population from pymoo.operators.crossover.binx import mut_binomial from pymoo.operators.crossover.expx import mut_exp -from pymoo.operators.repair.bounds_repair import is_out_of_bounds_by_problem, repair_random_init - - -def de_differential(X, F, dither=None, jitter=True, gamma=0.0001, return_differentials=False): - n_parents, n_matings, n_var = X.shape - assert n_parents % 2 == 1, "For the differential an odd number of values need to be provided" - - # make sure F is a one-dimensional vector - F = np.ones(n_matings) * F - - # build the pairs for the differentials - pairs = (np.arange(n_parents - 1) + 1).reshape(-1, 2) - - # the differentials from each pair subtraction - diffs = np.zeros((n_matings, n_var)) - # for each difference - for i, j in pairs: - if dither == "vector": - F = (F + np.random.random(n_matings) * (1 - F)) - elif dither == "scalar": - F = F + np.random.random() * (1 - F) - - # http://www.cs.ndsu.nodak.edu/~siludwig/Publish/papers/SSCI20141.pdf - if jitter: - F = (F * (1 + gamma * (np.random.random(n_matings) - 0.5))) - - # an add the difference to the first vector - diffs += F[:, None] * (X[i] - X[j]) - - # now add the differentials to the first parent - Xp = X[0] + diffs - - if return_differentials: - return Xp, diffs - else: - return Xp +# ========================================================================================================= +# Implementation +# ========================================================================================================= +class DEM: + + def __init__(self, + F=None, + gamma=1e-4, + de_repair="bounce-back", + **kwargs): + # Default value for F + if F is None: + F = (0.0, 1.0) + + # Define which method will be used to generate F values + if hasattr(F, "__iter__"): + self.scale_factor = self._randomize_scale_factor + else: + self.scale_factor = self._scalar_scale_factor + + # Define which method will be used to generate F values + if not hasattr(de_repair, "__call__"): + try: + de_repair = REPAIRS[de_repair] + except: + raise KeyError("Repair must be either callable or in " + str(list(REPAIRS.keys()))) + + # Define which strategy of rotation will be used + if gamma is None: + self.get_diff = self._diff_simple + else: + self.get_diff = self._diff_jitter + + self.F = F + self.gamma = gamma + self.de_repair = de_repair + + def do(self, problem, pop, parents, **kwargs): + + # Get all X values for mutation parents + Xr = pop.get("X")[parents.T].copy() + assert len(Xr.shape) == 3, "Please provide a three-dimensional matrix n_parents x pop_size x n_vars." + + # Create mutation vectors + V, diffs = self.de_mutation(Xr, return_differentials=True) + + # If the problem has boundaries to be considered + if problem.has_bounds(): + + # Do repair + V = self.de_repair(V, Xr[0], *problem.bounds()) + + return Population.new("X", V) + + def de_mutation(self, Xr, return_differentials=True): + + n_parents, n_matings, n_var = Xr.shape + assert n_parents % 2 == 1, "For the differential an odd number of values need to be provided" + + # Build the pairs for the differentials + pairs = (np.arange(n_parents - 1) + 1).reshape(-1, 2) + + # The differentials from each pair subtraction + diffs = self.get_diffs(Xr, pairs, n_matings, n_var) + + # Add the difference vectors to the base vector + V = Xr[0] + diffs + + if return_differentials: + return V, diffs + else: + return V + + def _randomize_scale_factor(self, n_matings): + return (self.F[0] + np.random.random(n_matings) * (self.F[1] - self.F[0])) + + def _scalar_scale_factor(self, n_matings): + return np.full(n_matings, self.F) + + def _diff_jitter(self, F, Xi, Xj, n_matings, n_var): + F = F[:, None] * (1 + self.gamma * (np.random.random((n_matings, n_var)) - 0.5)) + return F * (Xi - Xj) + + def _diff_simple(self, F, Xi, Xj, n_matings, n_var): + return F[:, None] * (Xi - Xj) + + def get_diffs(self, Xr, pairs, n_matings, n_var): + + # The differentials from each pair subtraction + diffs = np.zeros((n_matings, n_var)) + + # For each difference + for i, j in pairs: + + # Obtain F randomized in range + F = self.scale_factor(n_matings) + + # New difference vector + diff = self.get_diff(F, Xr[i], Xr[j], n_matings, n_var) + + # Add the difference to the first vector + diffs = diffs + diff + + return diffs + + class DEX(Crossover): - + def __init__(self, - F=None, - CR=0.7, variant="bin", - dither=None, - jitter=False, + CR=0.7, + F=None, + gamma=1e-4, n_diffs=1, - n_iter=1, at_least_once=True, + de_repair="bounce-back", **kwargs): - - super().__init__(1 + 2 * n_diffs, 1, **kwargs) - self.n_diffs = n_diffs - self.F = F + + # Default value for F + if F is None: + F = (0.0, 1.0) + + # Create instace for mutation + self.dem = DEM(F=F, + gamma=gamma, + de_repair=de_repair) + self.CR = CR self.variant = variant self.at_least_once = at_least_once - self.dither = dither - self.jitter = jitter - self.n_iter = n_iter - - def do(self, problem, pop, parents=None, **kwargs): - - # if a parents with array with mating indices is provided -> transform the input first - if parents is not None: - pop = [pop[mating] for mating in parents] - - # get the actual values from each of the parents - X = np.swapaxes(np.array([[parent.get("X") for parent in mating] for mating in pop]), 0, 1).copy() - - n_parents, n_matings, n_var = X.shape - - # a mask over matings that need to be repeated - m = np.arange(n_matings) - - # if the user provides directly an F value to use - F = self.F if self.F is not None else rnd_F(m) - - # prepare the out to be set - Xp = de_differential(X[:, m], F) - - # if the problem has boundaries to be considered - if problem.has_bounds(): - - for k in range(self.n_iter): - # find the individuals which are still infeasible - m = is_out_of_bounds_by_problem(problem, Xp) - - F = rnd_F(m) - - # actually execute the differential equation - Xp[m] = de_differential(X[:, m], F) - - # if still infeasible do a random initialization - Xp = repair_random_init(Xp, X[0], *problem.bounds()) - + + super().__init__(2 + 2 * n_diffs, 1, prob=1.0, **kwargs) + + def do(self, problem, pop, parents, **kwargs): + + # Get target vectors + X = pop.get("X")[parents[:, 0]] + + # About Xi + n_matings, n_var = X.shape + + # Obtain mutants + mutants = self.dem.do(problem, pop, parents[:, 1:], **kwargs) + + # Obtain V + V = mutants.get("X") + + # Binomial crossover if self.variant == "bin": M = mut_binomial(n_matings, n_var, self.CR, at_least_once=self.at_least_once) + # Exponential crossover elif self.variant == "exp": M = mut_exp(n_matings, n_var, self.CR, at_least_once=self.at_least_once) else: raise Exception(f"Unknown variant: {self.variant}") - # take the first parents (this is already a copy) - X = X[0] - - # set the corresponding values from the donor vector - X[M] = Xp[M] - - return Population.new("X", X) - + # Add mutated elements in corresponding main parent + X[M] = V[M] + + off = Population.new("X", X) + + return off + + +def bounce_back(X, Xb, xl, xu): + """Repair strategy + + Args: + X (2d array like): Mutated vectors including violations. + Xb (2d array like): Reference vectors for repair in feasible space. + xl (1d array like): Lower-bounds. + xu (1d array like): Upper-bounds. -def rnd_F(m): - return 0.5 * (1 + np.random.uniform(size=len(m))) + Returns: + 2d array like: Repaired vectors. + """ + + XL = xl[None, :].repeat(len(X), axis=0) + XU = xu[None, :].repeat(len(X), axis=0) + + i, j = np.where(X < XL) + if len(i) > 0: + X[i, j] = XL[i, j] + np.random.random(len(i)) * (Xb[i, j] - XL[i, j]) + + i, j = np.where(X > XU) + if len(i) > 0: + X[i, j] = XU[i, j] - np.random.random(len(i)) * (XU[i, j] - Xb[i, j]) + + return X + +def midway(X, Xb, xl, xu): + """Repair strategy + + Args: + X (2d array like): Mutated vectors including violations. + Xb (2d array like): Reference vectors for repair in feasible space. + xl (1d array like): Lower-bounds. + xu (1d array like): Upper-bounds. + + Returns: + 2d array like: Repaired vectors. + """ + + XL = xl[None, :].repeat(len(X), axis=0) + XU = xu[None, :].repeat(len(X), axis=0) + + i, j = np.where(X < XL) + if len(i) > 0: + X[i, j] = XL[i, j] + (Xb[i, j] - XL[i, j]) / 2 + + i, j = np.where(X > XU) + if len(i) > 0: + X[i, j] = XU[i, j] - (XU[i, j] - Xb[i, j]) / 2 + + return X + +def to_bounds(X, Xb, xl, xu): + """Repair strategy + + Args: + X (2d array like): Mutated vectors including violations. + Xb (2d array like): Reference vectors for repair in feasible space. + xl (1d array like): Lower-bounds. + xu (1d array like): Upper-bounds. + + Returns: + 2d array like: Repaired vectors. + """ + + XL = xl[None, :].repeat(len(X), axis=0) + XU = xu[None, :].repeat(len(X), axis=0) + + i, j = np.where(X < XL) + if len(i) > 0: + X[i, j] = XL[i, j] + + i, j = np.where(X > XU) + if len(i) > 0: + X[i, j] = XU[i, j] + + return X + +def rand_init(X, Xb, xl, xu): + """Repair strategy + + Args: + X (2d array like): Mutated vectors including violations. + Xb (2d array like): Reference vectors for repair in feasible space. + xl (1d array like): Lower-bounds. + xu (1d array like): Upper-bounds. + + Returns: + 2d array like: Repaired vectors. + """ + + XL = xl[None, :].repeat(len(X), axis=0) + XU = xu[None, :].repeat(len(X), axis=0) + + i, j = np.where(X < XL) + if len(i) > 0: + X[i, j] = XL[i, j] + np.random.random(len(i)) * (XU[i, j] - XL[i, j]) + + i, j = np.where(X > XU) + if len(i) > 0: + X[i, j] = XU[i, j] - np.random.random(len(i)) * (XU[i, j] - XL[i, j]) + + return X + + +def squared_bounce_back(X, Xb, xl, xu): + """Repair strategy + + Args: + X (2d array like): Mutated vectors including violations. + Xb (2d array like): Reference vectors for repair in feasible space. + xl (1d array like): Lower-bounds. + xu (1d array like): Upper-bounds. + + Returns: + 2d array like: Repaired vectors. + """ + + XL = xl[None, :].repeat(len(X), axis=0) + XU = xu[None, :].repeat(len(X), axis=0) + + i, j = np.where(X < XL) + if len(i) > 0: + X[i, j] = XL[i, j] + np.square(np.random.random(len(i))) * (Xb[i, j] - XL[i, j]) + + i, j = np.where(X > XU) + if len(i) > 0: + X[i, j] = XU[i, j] - np.square(np.random.random(len(i))) * (XU[i, j] - Xb[i, j]) + + return X + +def normalize_fun(fun): + + fmin = fun.min(axis=0) + fmax = fun.max(axis=0) + den = fmax - fmin + + den[den <= 1e-16] = 1.0 + + return (fun - fmin)/den + +REPAIRS = {"bounce-back":bounce_back, + "midway":midway, + "rand-init":rand_init, + "to-bounds":to_bounds} diff --git a/pymoo/operators/selection/des.py b/pymoo/operators/selection/des.py new file mode 100644 index 000000000..897eac412 --- /dev/null +++ b/pymoo/operators/selection/des.py @@ -0,0 +1,213 @@ +import numpy as np +from pymoo.core.selection import Selection + + +# ========================================================================================================= +# Implementation +# ========================================================================================================= + + +# This is the core differential evolution selection class +class DES(Selection): + + def __init__(self, + variant, + **kwargs): + + super().__init__() + self.variant = variant + + def _do(self, problem, pop, n_select, n_parents, **kwargs): + + # Obtain number of elements in population + n_pop = len(pop) + + # For most variants n_select must be equal to len(pop) + variant = self.variant + + if variant == "ranked": + """Proposed by Zhang et al. (2021). doi.org/10.1016/j.asoc.2021.107317""" + P = self._ranked(pop, n_select, n_parents) + + elif variant == "best": + P = self._best(pop, n_select, n_parents) + + elif variant == "current-to-best": + P = self._current_to_best(pop, n_select, n_parents) + + elif variant == "current-to-rand": + P = self._current_to_rand(pop, n_select, n_parents) + + else: + P = self._rand(pop, n_select, n_parents) + + return P + + def _rand(self, pop, n_select, n_parents, **kwargs): + + # len of pop + n_pop = len(pop) + + # Base form + P = np.empty([n_select, n_parents], dtype=int) + + # Fill first column with corresponding parent + P[:, 0] = np.arange(n_pop) + + # Fill next columns in loop + for j in range(1, n_parents): + + P[:, j] = np.random.choice(n_pop, n_select) + reselect = (P[:, j].reshape([-1, 1]) == P[:, :j]).any(axis=1) + + while np.any(reselect): + P[reselect, j] = np.random.choice(n_pop, reselect.sum()) + reselect = (P[:, j].reshape([-1, 1]) == P[:, :j]).any(axis=1) + + return P + + def _best(self, pop, n_select, n_parents, **kwargs): + + # len of pop + n_pop = len(pop) + + # Base form + P = np.empty([n_select, n_parents], dtype=int) + + # Fill first column with corresponding parent + P[:, 0] = np.arange(n_pop) + + # Fill first column with best candidate + P[:, 1] = 0 + + # Fill next columns in loop + for j in range(2, n_parents): + + P[:, j] = np.random.choice(n_pop, n_select) + reselect = (P[:, j].reshape([-1, 1]) == P[:, :j]).any(axis=1) + + while np.any(reselect): + P[reselect, j] = np.random.choice(n_pop, reselect.sum()) + reselect = (P[:, j].reshape([-1, 1]) == P[:, :j]).any(axis=1) + + return P + + def _current_to_best(self, pop, n_select, n_parents, **kwargs): + + # len of pop + n_pop = len(pop) + + # Base form + P = np.empty([n_select, n_parents], dtype=int) + + # Fill first column with corresponding parent + P[:, 0] = np.arange(n_pop) + + # Fill first column with current candidate + P[:, 1] = np.arange(n_pop) + + # Fill first direction from current + P[:, 3] = np.arange(n_pop) + + # Towards best + P[:, 2] = 0 + + # Fill next columns in loop + for j in range(4, n_parents): + + P[:, j] = np.random.choice(n_pop, n_select) + reselect = (P[:, j].reshape([-1, 1]) == P[:, :j]).any(axis=1) + + while np.any(reselect): + P[reselect, j] = np.random.choice(n_pop, reselect.sum()) + reselect = (P[:, j].reshape([-1, 1]) == P[:, :j]).any(axis=1) + + return P + + def _current_to_rand(self, pop, n_select, n_parents, **kwargs): + + # len of pop + n_pop = len(pop) + + # Base form + P = np.empty([n_select, n_parents], dtype=int) + + # Fill first column with corresponding parent + P[:, 0] = np.arange(n_pop) + + # Fill first column with current candidate + P[:, 1] = np.arange(n_pop) + + # Fill first direction from current + P[:, 3] = np.arange(n_pop) + + # Towards random + P[:, 2] = np.random.choice(n_pop, n_select) + reselect = (P[:, 2].reshape([-1, 1]) == P[:, [0, 1, 3]]).any(axis=1) + + while np.any(reselect): + P[reselect, 2] = np.random.choice(n_pop, reselect.sum()) + reselect = (P[:, 2].reshape([-1, 1]) == P[:, [0, 1, 3]]).any(axis=1) + + # Fill next columns in loop + for j in range(4, n_parents): + + P[:, j] = np.random.choice(n_pop, n_select) + reselect = (P[:, j].reshape([-1, 1]) == P[:, :j]).any(axis=1) + + while np.any(reselect): + P[reselect, j] = np.random.choice(n_pop, reselect.sum()) + reselect = (P[:, j].reshape([-1, 1]) == P[:, :j]).any(axis=1) + + return P + + def _ranked(self, pop, n_select, n_parents, **kwargs): + + P = self._rand(pop, n_select, n_parents, **kwargs) + P[:, 1:] = rank_sort(P[:, 1:], pop) + + return P + + +def ranks_from_cv(pop): + + ranks = pop.get("rank") + cv_elements = ranks == None + + if np.any(cv_elements): + ranks[cv_elements] = np.arange(len(pop))[cv_elements] + + return ranks + +def rank_sort(P, pop): + + ranks = ranks_from_cv(pop) + + sorted = np.argsort(ranks[P], axis=1, kind="stable") + S = np.take_along_axis(P, sorted, axis=1) + + P[:, 0] = S[:, 0] + + n_diffs = int((P.shape[1] - 1) / 2) + + for j in range(1, n_diffs + 1): + P[:, 2*j - 1] = S[:, j] + P[:, 2*j] = S[:, -j] + + return P + +def reiforce_directions(P, pop): + + ranks = ranks_from_cv(pop) + + ranks = ranks[P] + S = P.copy() + + n_diffs = int(P.shape[1] / 2) + + for j in range(0, n_diffs): + bad_directions = ranks[:, 2*j] > ranks[:, 2*j + 1] + P[bad_directions, 2*j] = S[bad_directions, 2*j + 1] + P[bad_directions, 2*j + 1] = S[bad_directions, 2*j] + + return P From 0ab60bd43c931e35331666b3a6bbe987a89ebb55 Mon Sep 17 00:00:00 2001 From: mooscalia project <93492480+mooscaliaproject@users.noreply.github.com> Date: Mon, 17 Oct 2022 13:47:51 -0300 Subject: [PATCH 02/23] Included GDE3PCD as a class --- pymoo/algorithms/moo/gde3.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pymoo/algorithms/moo/gde3.py b/pymoo/algorithms/moo/gde3.py index 588822cd2..00067f1f4 100644 --- a/pymoo/algorithms/moo/gde3.py +++ b/pymoo/algorithms/moo/gde3.py @@ -138,4 +138,10 @@ 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) \ No newline at end of file From 69b92a903a15afdfeeca25cdce373c590910ef15 Mon Sep 17 00:00:00 2001 From: mooscalia project <93492480+mooscaliaproject@users.noreply.github.com> Date: Tue, 1 Nov 2022 00:32:21 -0300 Subject: [PATCH 03/23] Fix exp crossover for new DE --- pymoo/operators/crossover/expx.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pymoo/operators/crossover/expx.py b/pymoo/operators/crossover/expx.py index 18c47e11d..dd0b96c73 100644 --- a/pymoo/operators/crossover/expx.py +++ b/pymoo/operators/crossover/expx.py @@ -6,7 +6,11 @@ def mut_exp(n_matings, n_var, prob, at_least_once=True): - assert len(prob) == n_matings + + if isinstance(np.float64(prob), float) or isinstance(np.float64(prob), int): + prob = np.ones(n_matings) * prob + else: + assert len(prob) == n_matings # the mask do to the crossover M = np.full((n_matings, n_var), False) From a0616bcd4ee004ff3414c8918be75f0dcf8ae737 Mon Sep 17 00:00:00 2001 From: mooscalia project <93492480+mooscaliaproject@users.noreply.github.com> Date: Tue, 1 Nov 2022 00:32:39 -0300 Subject: [PATCH 04/23] Include tests for soo DE --- tests/algorithms/test_de.py | 74 ++++++++++++++++++++++++++-------- tests/algorithms/test_de_ep.py | 25 ++++++++++++ 2 files changed, 83 insertions(+), 16 deletions(-) create mode 100644 tests/algorithms/test_de_ep.py diff --git a/tests/algorithms/test_de.py b/tests/algorithms/test_de.py index 426120e18..1f5ddd9ff 100644 --- a/tests/algorithms/test_de.py +++ b/tests/algorithms/test_de.py @@ -1,25 +1,67 @@ import pytest -from pymoo.algorithms.soo.nonconvex.de import DE -from pymoo.problems import get_problem -from pymoo.operators.sampling.lhs import LHS from pymoo.optimize import minimize +from pymoo.problems import get_problem +from pymoo.algorithms.soo.nonconvex.de import DE +from pymoo.algorithms.moo.gde3 import GDE3 -@pytest.mark.parametrize('selection', ["rand", "best", "target-to-best"]) +@pytest.mark.parametrize('selection', ["rand", "best", "current-to-best", "current-to-rand", "ranked"]) @pytest.mark.parametrize('crossover', ["bin", "exp"]) -def test_de(selection, crossover): - problem = get_problem("ackley", n_var=10) +@pytest.mark.parametrize('repair', ["bounce-back", "midway", "rand-init", "to-bounds"]) +def test_de_run(selection, crossover, repair): + problem = get_problem("rastrigin") + + NGEN = 30 + POPSIZE = 20 + SEED = 3 + + #DE Parameters + CR = 0.5 + F = (0.3, 1.0) + + de = DE(pop_size=POPSIZE, variant=f"DE/{selection}/1/{crossover}", CR=CR, F=F, repair=repair) + + res_de = minimize(problem, + de, + ('n_gen', NGEN), + seed=SEED, + save_history=False, + verbose=False) + + assert len(res_de.opt) > 0 + + +def test_de_perf(): + problem = get_problem("rastrigin") + + NGEN = 100 + POPSIZE = 20 + SEED = 3 + + #DE Parameters + CR = 0.5 + F = (0.3, 1.0) - algorithm = DE( - pop_size=100, - sampling=LHS(), - variant=f"DE/{selection}/1/{crossover}") + de = DE(pop_size=POPSIZE, variant="DE/rand/1/bin", CR=CR, F=F) - ret = minimize(problem, - algorithm, - ('n_gen', 20), - seed=1, - verbose=True) + res_de = minimize(problem, + de, + ('n_gen', NGEN), + seed=SEED, + save_history=False, + verbose=False) - assert len(ret.opt) > 0 + assert len(res_de.opt) > 0 + assert res_de.F <= 1e-6 + + gde3 = GDE3(pop_size=POPSIZE, variant="DE/rand/1/bin", CR=CR, F=F) + + res_gde3 = minimize(problem, + gde3, + ('n_gen', NGEN), + seed=SEED, + save_history=False, + verbose=False) + + assert res_gde3.F <= 1e-6 \ No newline at end of file diff --git a/tests/algorithms/test_de_ep.py b/tests/algorithms/test_de_ep.py new file mode 100644 index 000000000..3ebbf8c38 --- /dev/null +++ b/tests/algorithms/test_de_ep.py @@ -0,0 +1,25 @@ +import pytest + +from pymoo.algorithms.soo.nonconvex.de_ep import EPDE +from pymoo.problems import get_problem +from pymoo.operators.sampling.lhs import LHS +from pymoo.optimize import minimize + + +@pytest.mark.parametrize('selection', ["rand", "best", "target-to-best"]) +@pytest.mark.parametrize('crossover', ["bin", "exp"]) +def test_de(selection, crossover): + problem = get_problem("ackley", n_var=10) + + algorithm = EPDE( + pop_size=100, + sampling=LHS(), + variant=f"DE/{selection}/1/{crossover}") + + ret = minimize(problem, + algorithm, + ('n_gen', 20), + seed=1, + verbose=True) + + assert len(ret.opt) > 0 From cae48e209a835244daec7cbb3ef717faaabc9013 Mon Sep 17 00:00:00 2001 From: mooscalia project <93492480+mooscaliaproject@users.noreply.github.com> Date: Tue, 1 Nov 2022 00:44:18 -0300 Subject: [PATCH 05/23] Fix test DE repair --- tests/algorithms/test_de.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/algorithms/test_de.py b/tests/algorithms/test_de.py index 1f5ddd9ff..fc533729a 100644 --- a/tests/algorithms/test_de.py +++ b/tests/algorithms/test_de.py @@ -8,8 +8,8 @@ @pytest.mark.parametrize('selection', ["rand", "best", "current-to-best", "current-to-rand", "ranked"]) @pytest.mark.parametrize('crossover', ["bin", "exp"]) -@pytest.mark.parametrize('repair', ["bounce-back", "midway", "rand-init", "to-bounds"]) -def test_de_run(selection, crossover, repair): +@pytest.mark.parametrize('de_repair', ["bounce-back", "midway", "rand-init", "to-bounds"]) +def test_de_run(selection, crossover, de_repair): problem = get_problem("rastrigin") NGEN = 30 @@ -20,7 +20,7 @@ def test_de_run(selection, crossover, repair): CR = 0.5 F = (0.3, 1.0) - de = DE(pop_size=POPSIZE, variant=f"DE/{selection}/1/{crossover}", CR=CR, F=F, repair=repair) + de = DE(pop_size=POPSIZE, variant=f"DE/{selection}/1/{crossover}", CR=CR, F=F, de_repair=de_repair) res_de = minimize(problem, de, From cebc0b6eafc18677144d8d838269f9b0a3fea01f Mon Sep 17 00:00:00 2001 From: mooscalia project <93492480+mooscaliaproject@users.noreply.github.com> Date: Tue, 1 Nov 2022 00:44:29 -0300 Subject: [PATCH 06/23] Include tests MODE --- tests/algorithms/test_mode.py | 143 ++++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 tests/algorithms/test_mode.py diff --git a/tests/algorithms/test_mode.py b/tests/algorithms/test_mode.py new file mode 100644 index 000000000..4573fe65d --- /dev/null +++ b/tests/algorithms/test_mode.py @@ -0,0 +1,143 @@ +import pytest + +import numpy as np + +from pymoo.optimize import minimize +from pymoo.problems import get_problem +from pymoo.indicators.igd import IGD +from pymoo.algorithms.moo.nsder import NSDER +from pymoo.algorithms.moo.nsde import NSDE +from pymoo.algorithms.moo.gde3 import GDE3 +from pymoo.operators.survival.rank_and_crowding import RankAndCrowding, ConstrRankAndCrowding +from pymoo.util.ref_dirs import get_reference_directions + + +@pytest.mark.parametrize('survival', [RankAndCrowding, ConstrRankAndCrowding]) +@pytest.mark.parametrize('crowding_func', ["mnn", "2nn", "cd", "pcd", "ce"]) +def test_multi_run(survival, crowding_func): + + problem = get_problem("truss2d") + + NGEN = 250 + POPSIZE = 100 + SEED = 5 + + gde3 = GDE3(pop_size=POPSIZE, variant="DE/rand/1/bin", CR=0.5, F=(0.0, 0.9), de_repair="bounce-back", + survival=survival(crowding_func=crowding_func)) + + res_gde3 = minimize(problem, + gde3, + ('n_gen', NGEN), + seed=SEED, + save_history=False, + verbose=False) + + assert len(res_gde3.opt) > 0 + + +def test_multi_perf(): + + problem = get_problem("truss2d") + igd = IGD(pf=problem.pareto_front(), zero_to_one=True) + + NGEN = 250 + POPSIZE = 100 + SEED = 5 + + gde3 = GDE3(pop_size=POPSIZE, variant="DE/rand/1/bin", CR=0.5, F=(0.0, 0.9), de_repair="bounce-back", + survival=RankAndCrowding(crowding_func="cd")) + + res_gde3 = minimize(problem, + gde3, + ('n_gen', NGEN), + seed=SEED, + save_history=False, + verbose=False) + + igd_gde3 = igd.do(res_gde3.F) + assert igd_gde3 <= 0.08 + + gde3p = GDE3(pop_size=POPSIZE, variant="DE/rand/1/bin", CR=0.5, F=(0.0, 0.9), de_repair="bounce-back", + survival=RankAndCrowding(crowding_func="pcd")) + + res_gde3p = minimize(problem, + gde3p, + ('n_gen', NGEN), + seed=SEED, + save_history=False, + verbose=False) + + igd_gde3p = igd.do(res_gde3p.F) + assert igd_gde3p <= 0.01 + + nsde = NSDE(pop_size=POPSIZE, variant="DE/rand/1/bin", CR=0.5, F=(0.0, 0.9), de_repair="bounce-back", + survival=RankAndCrowding(crowding_func="pcd")) + + res_nsde = minimize(problem, + nsde, + ('n_gen', NGEN), + seed=SEED, + save_history=False, + verbose=False) + + igd_nsde = igd.do(res_nsde.F) + assert igd_nsde <= 0.01 + +@pytest.mark.parametrize('selection', ["rand", "current-to-rand", "ranked"]) +@pytest.mark.parametrize('crossover', ["bin", "exp"]) +@pytest.mark.parametrize('crowding_func', ["mnn", "2nn"]) +def test_many_run(selection, crossover, crowding_func): + + problem = get_problem("dtlz2") + + NGEN = 50 + POPSIZE = 136 + SEED = 5 + + gde3 = GDE3(pop_size=POPSIZE, variant=f"DE/{selection}/1/{crossover}", CR=0.2, F=(0.0, 1.0), gamma=1e-4, + survival=RankAndCrowding(crowding_func=crowding_func)) + + res_gde3 = minimize(problem, + gde3, + ('n_gen', NGEN), + seed=SEED, + save_history=False, + verbose=False) + + assert len(res_gde3.opt) > 0 + + +def test_many_perf(): + + problem = get_problem("dtlz2") + ref_dirs = get_reference_directions("das-dennis", 3, n_partitions=15) + igd = IGD(pf=problem.pareto_front(), zero_to_one=True) + + NGEN = 150 + POPSIZE = 136 + SEED = 5 + + gde3 = GDE3(pop_size=POPSIZE, variant="DE/rand/1/bin", CR=0.2, F=(0.0, 1.0), gamma=1e-4, + survival=RankAndCrowding(crowding_func="mnn")) + + res_gde3 = minimize(problem, + gde3, + ('n_gen', NGEN), + seed=SEED, + save_history=False, + verbose=False) + + igd_gde3 = igd.do(res_gde3.F) + assert igd_gde3 <= 0.07 + + nsder = NSDER(ref_dirs=ref_dirs, pop_size=POPSIZE, variant="DE/rand/1/bin", CR=0.5, F=(0.0, 1.0), gamma=1e-4) + + res_nsder = minimize(problem, + nsder, + ('n_gen', NGEN), + seed=SEED, + save_history=False, + verbose=False) + + igd_nsder = igd.do(res_nsder.F) + assert igd_nsder <= 0.01 \ No newline at end of file From 12737a0a4ede23c36adf3c524992aba8d1df97ce Mon Sep 17 00:00:00 2001 From: mooscalia project <93492480+mooscaliaproject@users.noreply.github.com> Date: Tue, 1 Nov 2022 01:14:57 -0300 Subject: [PATCH 07/23] Refactor DEX --- pymoo/operators/crossover/dex.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/pymoo/operators/crossover/dex.py b/pymoo/operators/crossover/dex.py index 3e2d5135f..aba6cbaf7 100644 --- a/pymoo/operators/crossover/dex.py +++ b/pymoo/operators/crossover/dex.py @@ -43,13 +43,12 @@ def __init__(self, self.F = F self.gamma = gamma self.de_repair = de_repair + + def __call__(self, problem, pop, parents, **kwargs): + return self.do(problem, pop, parents, **kwargs) - def do(self, problem, pop, parents, **kwargs): + def do(self, problem, Xr, **kwargs): - # Get all X values for mutation parents - Xr = pop.get("X")[parents.T].copy() - assert len(Xr.shape) == 3, "Please provide a three-dimensional matrix n_parents x pop_size x n_vars." - # Create mutation vectors V, diffs = self.de_mutation(Xr, return_differentials=True) @@ -139,17 +138,19 @@ def __init__(self, self.at_least_once = at_least_once super().__init__(2 + 2 * n_diffs, 1, prob=1.0, **kwargs) + - def do(self, problem, pop, parents, **kwargs): + def _do(self, problem, X, **kwargs): # Get target vectors - X = pop.get("X")[parents[:, 0]] + Xp = X[0] + Xr = X[1:] # About Xi n_matings, n_var = X.shape # Obtain mutants - mutants = self.dem.do(problem, pop, parents[:, 1:], **kwargs) + mutants = self.dem.do(problem, Xr, **kwargs) # Obtain V V = mutants.get("X") From cf99048b8bce10cfbd9b93ecde6a8014ee91aefb Mon Sep 17 00:00:00 2001 From: mooscalia project <93492480+mooscaliaproject@users.noreply.github.com> Date: Tue, 1 Nov 2022 01:19:34 -0300 Subject: [PATCH 08/23] Fix new DEX --- pymoo/operators/crossover/dex.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pymoo/operators/crossover/dex.py b/pymoo/operators/crossover/dex.py index aba6cbaf7..2615d936c 100644 --- a/pymoo/operators/crossover/dex.py +++ b/pymoo/operators/crossover/dex.py @@ -147,7 +147,7 @@ def _do(self, problem, X, **kwargs): Xr = X[1:] # About Xi - n_matings, n_var = X.shape + n_matings, n_var = Xp.shape # Obtain mutants mutants = self.dem.do(problem, Xr, **kwargs) @@ -165,9 +165,9 @@ def _do(self, problem, X, **kwargs): raise Exception(f"Unknown variant: {self.variant}") # Add mutated elements in corresponding main parent - X[M] = V[M] + Xp[M] = V[M] - off = Population.new("X", X) + off = Population.new("X", Xp) return off From e5d27338a5ef77fd02f7ce84acd8194410c8f774 Mon Sep 17 00:00:00 2001 From: mooscalia project <93492480+mooscaliaproject@users.noreply.github.com> Date: Tue, 1 Nov 2022 01:35:47 -0300 Subject: [PATCH 09/23] Revert DEX arguments --- pymoo/operators/crossover/dex.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/pymoo/operators/crossover/dex.py b/pymoo/operators/crossover/dex.py index 2615d936c..8fc802473 100644 --- a/pymoo/operators/crossover/dex.py +++ b/pymoo/operators/crossover/dex.py @@ -47,9 +47,13 @@ def __init__(self, def __call__(self, problem, pop, parents, **kwargs): return self.do(problem, pop, parents, **kwargs) - def do(self, problem, Xr, **kwargs): + def do(self, problem, pop, parents, **kwargs): - # Create mutation vectors + #Get all X values for mutation parents + Xr = pop.get("X")[parents.T].copy() + assert len(Xr.shape) == 3, "Please provide a three-dimensional matrix n_parents x pop_size x n_vars." + + #Create mutation vectors V, diffs = self.de_mutation(Xr, return_differentials=True) # If the problem has boundaries to be considered @@ -140,17 +144,16 @@ def __init__(self, super().__init__(2 + 2 * n_diffs, 1, prob=1.0, **kwargs) - def _do(self, problem, X, **kwargs): + def do(self, problem, pop, parents, **kwargs): - # Get target vectors - Xp = X[0] - Xr = X[1:] + #Get target vectors + X = pop.get("X")[parents[:, 0]] - # About Xi - n_matings, n_var = Xp.shape + #About Xi + n_matings, n_var = X.shape - # Obtain mutants - mutants = self.dem.do(problem, Xr, **kwargs) + #Obtain mutants + mutants = self.dem.do(problem, pop, parents[:, 1:], **kwargs) # Obtain V V = mutants.get("X") From c57f47849764565bed233ba2900f7f4e947886dd Mon Sep 17 00:00:00 2001 From: mooscalia project <93492480+mooscaliaproject@users.noreply.github.com> Date: Tue, 1 Nov 2022 01:36:01 -0300 Subject: [PATCH 10/23] Remove DEX from test_crossover --- tests/operators/test_crossover.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/operators/test_crossover.py b/tests/operators/test_crossover.py index ff022c079..f123fb65d 100644 --- a/tests/operators/test_crossover.py +++ b/tests/operators/test_crossover.py @@ -5,8 +5,6 @@ from pymoo.operators.crossover.ox import OrderCrossover from pymoo.operators.crossover.spx import SPX -from pymoo.operators.crossover.dex import DEX - from pymoo.algorithms.moo.nsga2 import NSGA2 from pymoo.algorithms.soo.nonconvex.ga import GA from pymoo.operators.crossover.pntx import TwoPointCrossover @@ -19,7 +17,7 @@ from pymoo.problems.single.traveling_salesman import create_random_tsp_problem -@pytest.mark.parametrize('crossover', [DEX(), SBX()]) +@pytest.mark.parametrize('crossover', [SBX()]) def test_crossover_real(crossover): method = GA(pop_size=20, crossover=crossover) minimize(get_problem("sphere"), method, ("n_gen", 20)) From 25eea6dbaabe6ebd09931740f9610c8398fd9f93 Mon Sep 17 00:00:00 2001 From: mooscalia project <93492480+mooscaliaproject@users.noreply.github.com> Date: Tue, 1 Nov 2022 01:39:50 -0300 Subject: [PATCH 11/23] Fix DEX do --- pymoo/operators/crossover/dex.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pymoo/operators/crossover/dex.py b/pymoo/operators/crossover/dex.py index 8fc802473..ef13d8202 100644 --- a/pymoo/operators/crossover/dex.py +++ b/pymoo/operators/crossover/dex.py @@ -168,9 +168,9 @@ def do(self, problem, pop, parents, **kwargs): raise Exception(f"Unknown variant: {self.variant}") # Add mutated elements in corresponding main parent - Xp[M] = V[M] + X[M] = V[M] - off = Population.new("X", Xp) + off = Population.new("X", X) return off From c8b1431cc8943a2af95c04daf38df2bd93dea072 Mon Sep 17 00:00:00 2001 From: mooscalia project <93492480+mooscaliaproject@users.noreply.github.com> Date: Tue, 1 Nov 2022 18:23:43 -0300 Subject: [PATCH 12/23] Refactor DEX and DEM to match SBX format --- pymoo/algorithms/soo/nonconvex/de.py | 11 ++++----- pymoo/operators/crossover/dex.py | 37 +++++++++++++++++----------- tests/operators/test_crossover.py | 3 ++- 3 files changed, 30 insertions(+), 21 deletions(-) diff --git a/pymoo/algorithms/soo/nonconvex/de.py b/pymoo/algorithms/soo/nonconvex/de.py index a01c39fc3..d61801183 100755 --- a/pymoo/algorithms/soo/nonconvex/de.py +++ b/pymoo/algorithms/soo/nonconvex/de.py @@ -41,7 +41,7 @@ def __init__(self, if F is None: F = (0.0, 1.0) - # Define crossover strategy + # Define crossover strategy (DE mutation is included) self.crossover = DEX(variant=crossover_variant, CR=CR, F=F, @@ -57,15 +57,14 @@ def __init__(self, def do(self, problem, pop, n_offsprings, **kwargs): # Select parents including donor vector - parents = self.selection.do(problem, pop, n_offsprings, self.crossover.n_parents, - to_pop=False, **kwargs) + parents = self.selection(problem, pop, n_offsprings, self.crossover.n_parents, to_pop=True, **kwargs) # Perform mutation included in DEX and crossover - off = self.crossover.do(problem, pop, parents, **kwargs) + off = self.crossover(problem, parents, **kwargs) # Perform posterior mutation and repair if passed - off = self.mutation.do(problem, off) - off = self.repair.do(problem, off) + off = self.mutation(problem, off) + off = self.repair(problem, off) return off diff --git a/pymoo/operators/crossover/dex.py b/pymoo/operators/crossover/dex.py index ef13d8202..67a487965 100644 --- a/pymoo/operators/crossover/dex.py +++ b/pymoo/operators/crossover/dex.py @@ -9,12 +9,13 @@ # Implementation # ========================================================================================================= -class DEM: +class DEM(Crossover): def __init__(self, F=None, gamma=1e-4, de_repair="bounce-back", + n_diffs=1, **kwargs): # Default value for F @@ -43,17 +44,20 @@ def __init__(self, self.F = F self.gamma = gamma self.de_repair = de_repair + + super().__init__(1 + 2 * n_diffs, 1, prob=1.0, **kwargs) - def __call__(self, problem, pop, parents, **kwargs): - return self.do(problem, pop, parents, **kwargs) - def do(self, problem, pop, parents, **kwargs): - - #Get all X values for mutation parents - Xr = pop.get("X")[parents.T].copy() - assert len(Xr.shape) == 3, "Please provide a three-dimensional matrix n_parents x pop_size x n_vars." + def do(self, problem, pop, parents=None, **kwargs): - #Create mutation vectors + # Convert pop if parents is not None + if not parents is None: + pop = pop[parents] + + # Get all X values for mutation parents + Xr = np.swapaxes(pop, 0, 1).get("X") + + # Create mutation vectors V, diffs = self.de_mutation(Xr, return_differentials=True) # If the problem has boundaries to be considered @@ -135,7 +139,8 @@ def __init__(self, # Create instace for mutation self.dem = DEM(F=F, gamma=gamma, - de_repair=de_repair) + de_repair=de_repair, + n_diffs=n_diffs) self.CR = CR self.variant = variant @@ -144,16 +149,20 @@ def __init__(self, super().__init__(2 + 2 * n_diffs, 1, prob=1.0, **kwargs) - def do(self, problem, pop, parents, **kwargs): + def do(self, problem, pop, parents=None, **kwargs): + + # Convert pop if parents is not None + if not parents is None: + pop = pop[parents] - #Get target vectors - X = pop.get("X")[parents[:, 0]] + # Get all X values for mutation parents + X = pop[:, 0].get("X") #About Xi n_matings, n_var = X.shape #Obtain mutants - mutants = self.dem.do(problem, pop, parents[:, 1:], **kwargs) + mutants = self.dem.do(problem, pop[:, 1:], **kwargs) # Obtain V V = mutants.get("X") diff --git a/tests/operators/test_crossover.py b/tests/operators/test_crossover.py index f123fb65d..1ec45e536 100644 --- a/tests/operators/test_crossover.py +++ b/tests/operators/test_crossover.py @@ -9,6 +9,7 @@ from pymoo.algorithms.soo.nonconvex.ga import GA from pymoo.operators.crossover.pntx import TwoPointCrossover from pymoo.operators.crossover.sbx import SBX +from pymoo.operators.crossover.dex import DEM, DEX from pymoo.operators.crossover.ux import UX from pymoo.operators.mutation.inversion import InversionMutation from pymoo.operators.sampling.rnd import PermutationRandomSampling @@ -17,7 +18,7 @@ from pymoo.problems.single.traveling_salesman import create_random_tsp_problem -@pytest.mark.parametrize('crossover', [SBX()]) +@pytest.mark.parametrize('crossover', [DEX(), DEM(), SBX()]) def test_crossover_real(crossover): method = GA(pop_size=20, crossover=crossover) minimize(get_problem("sphere"), method, ("n_gen", 20)) From fbe12daf1a451dbab8c25f4b1330410e5572b30e Mon Sep 17 00:00:00 2001 From: mooscalia project <93492480+mooscaliaproject@users.noreply.github.com> Date: Tue, 1 Nov 2022 19:08:32 -0300 Subject: [PATCH 13/23] Fix DE de_repair on mating --- pymoo/algorithms/soo/nonconvex/de.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymoo/algorithms/soo/nonconvex/de.py b/pymoo/algorithms/soo/nonconvex/de.py index d61801183..5101f24d0 100755 --- a/pymoo/algorithms/soo/nonconvex/de.py +++ b/pymoo/algorithms/soo/nonconvex/de.py @@ -48,7 +48,7 @@ def __init__(self, gamma=gamma, n_diffs=n_diffs, at_least_once=True, - repair=repair) + de_repair=de_repair) # Define posterior mutation strategy and repair self.mutation = mutation if mutation is not None else NoMutation() From edbaec9c0f8b2797708c27ea47d3a771277824ed Mon Sep 17 00:00:00 2001 From: mooscalia project <93492480+mooscaliaproject@users.noreply.github.com> Date: Wed, 2 Nov 2022 17:06:54 -0300 Subject: [PATCH 14/23] Refactor infill of DE new class with inheritance --- pymoo/algorithms/soo/nonconvex/de.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/pymoo/algorithms/soo/nonconvex/de.py b/pymoo/algorithms/soo/nonconvex/de.py index 5101f24d0..57d8b13c7 100755 --- a/pymoo/algorithms/soo/nonconvex/de.py +++ b/pymoo/algorithms/soo/nonconvex/de.py @@ -3,19 +3,19 @@ from pymoo.algorithms.soo.nonconvex.ga import FitnessSurvival from pymoo.core.replacement import ImprovementReplacement from pymoo.operators.mutation.nom import NoMutation -from pymoo.core.repair import NoRepair from pymoo.operators.sampling.lhs import LHS from pymoo.termination.default import DefaultSingleObjectiveTermination from pymoo.util.display.single import SingleObjectiveOutput from pymoo.operators.selection.des import DES from pymoo.operators.crossover.dex import DEX +from pymoo.core.infill import InfillCriterion # ========================================================================================================= # Implementation # ========================================================================================================= -class InfillDE: +class VariantDE(InfillCriterion): def __init__(self, variant="DE/rand/1/bin", @@ -23,8 +23,7 @@ def __init__(self, F=(0.5, 1.0), gamma=1e-4, de_repair="bounce-back", - mutation=None, - repair=None): + mutation=None): # Parse the information from the string _, selection_variant, n_diff, crossover_variant, = variant.split("/") @@ -37,7 +36,7 @@ def __init__(self, # Define parent selection operator self.selection = DES(selection_variant) - #Default value for F + # Default value for F if F is None: F = (0.0, 1.0) @@ -52,9 +51,9 @@ def __init__(self, # Define posterior mutation strategy and repair self.mutation = mutation if mutation is not None else NoMutation() - self.repair = repair if repair is not None else NoRepair() + - def do(self, problem, pop, n_offsprings, **kwargs): + def _do(self, problem, pop, n_offsprings, **kwargs): # Select parents including donor vector parents = self.selection(problem, pop, n_offsprings, self.crossover.n_parents, to_pop=True, **kwargs) @@ -64,7 +63,6 @@ def do(self, problem, pop, n_offsprings, **kwargs): # Perform posterior mutation and repair if passed off = self.mutation(problem, off) - off = self.repair(problem, off) return off From 8fdd853792da0ea5e55e68b7cc75def28aae3448 Mon Sep 17 00:00:00 2001 From: mooscalia project <93492480+mooscaliaproject@users.noreply.github.com> Date: Wed, 2 Nov 2022 17:09:34 -0300 Subject: [PATCH 15/23] Fix DE algorithms with new mating --- pymoo/algorithms/moo/nsde.py | 16 ++++++++-------- pymoo/algorithms/soo/nonconvex/de.py | 14 +++++++------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/pymoo/algorithms/moo/nsde.py b/pymoo/algorithms/moo/nsde.py index e730350e6..69d581d39 100644 --- a/pymoo/algorithms/moo/nsde.py +++ b/pymoo/algorithms/moo/nsde.py @@ -1,6 +1,6 @@ from pymoo.algorithms.moo.nsga2 import NSGA2 from pymoo.operators.sampling.lhs import LHS -from pymoo.algorithms.soo.nonconvex.de import InfillDE +from pymoo.algorithms.soo.nonconvex.de import VariantDE from pymoo.operators.survival.rank_and_crowding import RankAndCrowding @@ -90,13 +90,13 @@ def __init__(self, n_offsprings = pop_size #Mating - mating = InfillDE(variant=variant, - CR=CR, - F=F, - gamma=gamma, - de_repair=de_repair, - mutation=mutation, - repair=repair) + mating = VariantDE(variant=variant, + CR=CR, + F=F, + gamma=gamma, + de_repair=de_repair, + mutation=mutation, + repair=repair) #Init from pymoo's NSGA2 super().__init__(pop_size=pop_size, diff --git a/pymoo/algorithms/soo/nonconvex/de.py b/pymoo/algorithms/soo/nonconvex/de.py index 57d8b13c7..52766c920 100755 --- a/pymoo/algorithms/soo/nonconvex/de.py +++ b/pymoo/algorithms/soo/nonconvex/de.py @@ -136,13 +136,13 @@ def __init__(self, Pymoo's repair operator after mutation. Defaults to NoRepair(). """ - mating = InfillDE(variant=variant, - CR=CR, - F=F, - gamma=gamma, - de_repair=de_repair, - mutation=mutation, - repair=repair) + mating = VariantDE(variant=variant, + CR=CR, + F=F, + gamma=gamma, + de_repair=de_repair, + mutation=mutation, + repair=repair) # Number of offsprings at each generation n_offsprings = pop_size From 4646879f3af0ea79f24b355a5e5a33e5f3f79b57 Mon Sep 17 00:00:00 2001 From: mooscalia project <93492480+mooscaliaproject@users.noreply.github.com> Date: Wed, 2 Nov 2022 17:32:31 -0300 Subject: [PATCH 16/23] Fix kwargs passed to DE variant --- pymoo/algorithms/soo/nonconvex/de.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/pymoo/algorithms/soo/nonconvex/de.py b/pymoo/algorithms/soo/nonconvex/de.py index 52766c920..73be395dd 100755 --- a/pymoo/algorithms/soo/nonconvex/de.py +++ b/pymoo/algorithms/soo/nonconvex/de.py @@ -23,7 +23,11 @@ def __init__(self, F=(0.5, 1.0), gamma=1e-4, de_repair="bounce-back", - mutation=None): + mutation=None, + **kwargs): + + # Default initialization of InfillCriterion + super().__init__(eliminate_duplicates=False, **kwargs) # Parse the information from the string _, selection_variant, n_diff, crossover_variant, = variant.split("/") @@ -77,8 +81,6 @@ def __init__(self, F=(0.5, 1.0), gamma=1e-4, de_repair="bounce-back", - mutation=None, - repair=None, output=SingleObjectiveOutput(), **kwargs): """ @@ -141,8 +143,7 @@ def __init__(self, F=F, gamma=gamma, de_repair=de_repair, - mutation=mutation, - repair=repair) + **kwargs) # Number of offsprings at each generation n_offsprings = pop_size @@ -162,7 +163,7 @@ def _initialize_advance(self, infills=None, **kwargs): def _infill(self): - infills = self.mating.do(self.problem, self.pop, self.n_offsprings, algorithm=self) + infills = self.mating(self.problem, self.pop, self.n_offsprings, algorithm=self) return infills From 884cb02c1b15caa43051fe69b8582bfe61ec73b3 Mon Sep 17 00:00:00 2001 From: mooscalia project <93492480+mooscaliaproject@users.noreply.github.com> Date: Wed, 2 Nov 2022 17:35:54 -0300 Subject: [PATCH 17/23] Style comments --- pymoo/algorithms/soo/nonconvex/de.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pymoo/algorithms/soo/nonconvex/de.py b/pymoo/algorithms/soo/nonconvex/de.py index 73be395dd..375002653 100755 --- a/pymoo/algorithms/soo/nonconvex/de.py +++ b/pymoo/algorithms/soo/nonconvex/de.py @@ -171,11 +171,11 @@ def _advance(self, infills=None, **kwargs): assert infills is not None, "This algorithms uses the AskAndTell interface thus infills must be provided." - #One-to-one replacement survival + # One-to-one replacement survival self.pop = ImprovementReplacement().do(self.problem, self.pop, infills) - #Sort the population by fitness to make the selection simpler for mating (not an actual survival, just sorting) + # Sort the population by fitness to make the selection simpler for mating (not an actual survival, just sorting) self.pop = FitnessSurvival().do(self.problem, self.pop) - #Set ranks + # Set ranks self.pop.set("rank", np.arange(self.pop_size)) From f2c3d502f479c3ba71f0f237d189d7536d40cdac Mon Sep 17 00:00:00 2001 From: mooscalia project <93492480+mooscaliaproject@users.noreply.github.com> Date: Wed, 2 Nov 2022 17:48:02 -0300 Subject: [PATCH 18/23] Fix eliminate duplicates None --- pymoo/algorithms/moo/nsde.py | 15 +++++---------- pymoo/algorithms/soo/nonconvex/de.py | 4 ++-- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/pymoo/algorithms/moo/nsde.py b/pymoo/algorithms/moo/nsde.py index 69d581d39..e0224340c 100644 --- a/pymoo/algorithms/moo/nsde.py +++ b/pymoo/algorithms/moo/nsde.py @@ -13,14 +13,11 @@ 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", - mutation=None, - repair=None, survival=RankAndCrowding(), **kwargs): """ @@ -86,23 +83,21 @@ def __init__(self, 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 + # Number of offsprings at each generation n_offsprings = pop_size - #Mating + # Mating mating = VariantDE(variant=variant, CR=CR, F=F, gamma=gamma, de_repair=de_repair, - mutation=mutation, - repair=repair) + **kwargs) - #Init from pymoo's NSGA2 + # Init from pymoo's NSGA2 super().__init__(pop_size=pop_size, - sampling=sampling, mating=mating, survival=survival, - eliminate_duplicates=False, + eliminate_duplicates=None, n_offsprings=n_offsprings, **kwargs) diff --git a/pymoo/algorithms/soo/nonconvex/de.py b/pymoo/algorithms/soo/nonconvex/de.py index 375002653..c667060b6 100755 --- a/pymoo/algorithms/soo/nonconvex/de.py +++ b/pymoo/algorithms/soo/nonconvex/de.py @@ -27,7 +27,7 @@ def __init__(self, **kwargs): # Default initialization of InfillCriterion - super().__init__(eliminate_duplicates=False, **kwargs) + super().__init__(eliminate_duplicates=None, **kwargs) # Parse the information from the string _, selection_variant, n_diff, crossover_variant, = variant.split("/") @@ -152,7 +152,7 @@ def __init__(self, sampling=sampling, mating=mating, n_offsprings=n_offsprings, - eliminate_duplicates=False, + eliminate_duplicates=None, output=output, **kwargs) From c8d215ceef09cf4973f454f6173af5e1cc4f0258 Mon Sep 17 00:00:00 2001 From: mooscalia project <93492480+mooscaliaproject@users.noreply.github.com> Date: Wed, 2 Nov 2022 17:50:08 -0300 Subject: [PATCH 19/23] Restore sampling LHS on NSDE --- pymoo/algorithms/moo/nsde.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pymoo/algorithms/moo/nsde.py b/pymoo/algorithms/moo/nsde.py index e0224340c..79b012d1d 100644 --- a/pymoo/algorithms/moo/nsde.py +++ b/pymoo/algorithms/moo/nsde.py @@ -13,6 +13,7 @@ class NSDE(NSGA2): def __init__(self, pop_size=100, + sampling=LHS(), variant="DE/rand/1/bin", CR=0.7, F=None, @@ -96,6 +97,7 @@ def __init__(self, # Init from pymoo's NSGA2 super().__init__(pop_size=pop_size, + sampling=sampling, mating=mating, survival=survival, eliminate_duplicates=None, From e5baab7823b64eef08e3665bc72b96410c9cb9dd Mon Sep 17 00:00:00 2001 From: mooscalia project <93492480+mooscaliaproject@users.noreply.github.com> Date: Wed, 2 Nov 2022 17:55:45 -0300 Subject: [PATCH 20/23] Style sampling of NSDER --- pymoo/algorithms/moo/nsder.py | 1 - pymoo/algorithms/soo/nonconvex/de.py | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/pymoo/algorithms/moo/nsder.py b/pymoo/algorithms/moo/nsder.py index e5e0349e1..000342450 100644 --- a/pymoo/algorithms/moo/nsder.py +++ b/pymoo/algorithms/moo/nsder.py @@ -13,7 +13,6 @@ class NSDER(NSDE): def __init__(self, ref_dirs, pop_size=100, - sampling=LHS(), variant="DE/rand/1/bin", CR=0.7, F=None, diff --git a/pymoo/algorithms/soo/nonconvex/de.py b/pymoo/algorithms/soo/nonconvex/de.py index c667060b6..c48499b61 100755 --- a/pymoo/algorithms/soo/nonconvex/de.py +++ b/pymoo/algorithms/soo/nonconvex/de.py @@ -138,6 +138,7 @@ def __init__(self, Pymoo's repair operator after mutation. Defaults to NoRepair(). """ + # Mating mating = VariantDE(variant=variant, CR=CR, F=F, From 977f7583e81e5afebd0e984686c38f10046d5110 Mon Sep 17 00:00:00 2001 From: mooscalia project <93492480+mooscaliaproject@users.noreply.github.com> Date: Wed, 2 Nov 2022 17:58:58 -0300 Subject: [PATCH 21/23] Fix test DE GDE3 assertion --- tests/algorithms/test_de.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/algorithms/test_de.py b/tests/algorithms/test_de.py index fc533729a..96335dbc9 100644 --- a/tests/algorithms/test_de.py +++ b/tests/algorithms/test_de.py @@ -1,5 +1,6 @@ import pytest +import numpy as np from pymoo.optimize import minimize from pymoo.problems import get_problem from pymoo.algorithms.soo.nonconvex.de import DE @@ -64,4 +65,4 @@ def test_de_perf(): save_history=False, verbose=False) - assert res_gde3.F <= 1e-6 \ No newline at end of file + assert np.all(res_gde3.F <= 1e-6) \ No newline at end of file From 3536c233eb421e592dbaa1b37732c6b0e39d4755 Mon Sep 17 00:00:00 2001 From: mooscalia project <93492480+mooscaliaproject@users.noreply.github.com> Date: Wed, 2 Nov 2022 18:03:54 -0300 Subject: [PATCH 22/23] Fix sampling on NSDER init --- pymoo/algorithms/moo/nsder.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pymoo/algorithms/moo/nsder.py b/pymoo/algorithms/moo/nsder.py index 000342450..d0abd821c 100644 --- a/pymoo/algorithms/moo/nsder.py +++ b/pymoo/algorithms/moo/nsder.py @@ -100,7 +100,6 @@ def __init__(self, survival = ReferenceDirectionSurvival(ref_dirs) super().__init__(pop_size=pop_size, - sampling=sampling, variant=variant, CR=CR, F=F, From 3cb4f2b94a00d487dedeb308b0bfd5080f073868 Mon Sep 17 00:00:00 2001 From: mooscalia project <93492480+mooscaliaproject@users.noreply.github.com> Date: Fri, 18 Nov 2022 00:19:42 -0300 Subject: [PATCH 23/23] Style code formatting with autopep8 --- pymoo/algorithms/moo/gde3.py | 63 +++++++------- pymoo/algorithms/moo/nsde.py | 38 ++++---- pymoo/algorithms/moo/nsder.py | 39 +++++---- pymoo/algorithms/soo/nonconvex/de.py | 61 +++++++------ pymoo/operators/crossover/dex.py | 119 +++++++++++++------------ pymoo/operators/selection/des.py | 126 ++++++++++++++------------- 6 files changed, 226 insertions(+), 220 deletions(-) diff --git a/pymoo/algorithms/moo/gde3.py b/pymoo/algorithms/moo/gde3.py index 00067f1f4..636086c5a 100644 --- a/pymoo/algorithms/moo/gde3.py +++ b/pymoo/algorithms/moo/gde3.py @@ -10,7 +10,7 @@ class GDE3(NSDE): - + def __init__(self, pop_size=100, variant="DE/rand/1/bin", @@ -21,11 +21,11 @@ def __init__(self, """ 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. @@ -34,13 +34,13 @@ def __init__(self, ---------- 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' @@ -48,43 +48,43 @@ def __init__(self, - '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, @@ -93,55 +93,56 @@ def __init__(self, **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 + # 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 + # 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 + # Check whether the new solution dominates the parent or not rel = get_relation(parent, off) - #If indifferent we add both + # If indifferent we add both if rel == 0: survivors.extend([parent, off]) - #If offspring dominates parent + # If offspring dominates parent elif rel == -1: survivors.append(off) - #If parent dominates offspring + # If parent dominates offspring else: survivors.append(parent) - #Create the population + # Create the population survivors = Population.create(*survivors) - #Perform a survival to reduce to pop size + # 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) \ No newline at end of file + super().__init__(pop_size, variant, CR, F, gamma, survival=survival, **kwargs) diff --git a/pymoo/algorithms/moo/nsde.py b/pymoo/algorithms/moo/nsde.py index 79b012d1d..8bf11596c 100644 --- a/pymoo/algorithms/moo/nsde.py +++ b/pymoo/algorithms/moo/nsde.py @@ -10,7 +10,7 @@ class NSDE(NSGA2): - + def __init__(self, pop_size=100, sampling=LHS(), @@ -24,22 +24,22 @@ def __init__(self, """ 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' @@ -47,46 +47,46 @@ def __init__(self, - '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, @@ -94,7 +94,7 @@ def __init__(self, gamma=gamma, de_repair=de_repair, **kwargs) - + # Init from pymoo's NSGA2 super().__init__(pop_size=pop_size, sampling=sampling, diff --git a/pymoo/algorithms/moo/nsder.py b/pymoo/algorithms/moo/nsder.py index d0abd821c..a0a8f5de5 100644 --- a/pymoo/algorithms/moo/nsder.py +++ b/pymoo/algorithms/moo/nsder.py @@ -8,8 +8,9 @@ # Implementation # ========================================================================================================= + class NSDER(NSDE): - + def __init__(self, ref_dirs, pop_size=100, @@ -20,23 +21,23 @@ def __init__(self, **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' @@ -44,42 +45,42 @@ def __init__(self, - '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: @@ -98,7 +99,7 @@ def __init__(self, del kwargs['survival'] else: survival = ReferenceDirectionSurvival(ref_dirs) - + super().__init__(pop_size=pop_size, variant=variant, CR=CR, @@ -114,7 +115,7 @@ def _setup(self, problem, **kwargs): 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"))]] diff --git a/pymoo/algorithms/soo/nonconvex/de.py b/pymoo/algorithms/soo/nonconvex/de.py index c48499b61..d4ae4a586 100755 --- a/pymoo/algorithms/soo/nonconvex/de.py +++ b/pymoo/algorithms/soo/nonconvex/de.py @@ -16,7 +16,7 @@ # ========================================================================================================= class VariantDE(InfillCriterion): - + def __init__(self, variant="DE/rand/1/bin", CR=0.7, @@ -25,25 +25,25 @@ def __init__(self, de_repair="bounce-back", mutation=None, **kwargs): - + # Default initialization of InfillCriterion super().__init__(eliminate_duplicates=None, **kwargs) - + # Parse the information from the string _, selection_variant, n_diff, crossover_variant, = variant.split("/") n_diffs = int(n_diff) - + # When "to" in variant there are more than 1 difference vectors if "-to-" in variant: n_diffs += 1 - + # Define parent selection operator self.selection = DES(selection_variant) - + # Default value for F if F is None: F = (0.0, 1.0) - + # Define crossover strategy (DE mutation is included) self.crossover = DEX(variant=crossover_variant, CR=CR, @@ -52,24 +52,23 @@ def __init__(self, n_diffs=n_diffs, at_least_once=True, de_repair=de_repair) - + # Define posterior mutation strategy and repair self.mutation = mutation if mutation is not None else NoMutation() - def _do(self, problem, pop, n_offsprings, **kwargs): - + # Select parents including donor vector parents = self.selection(problem, pop, n_offsprings, self.crossover.n_parents, to_pop=True, **kwargs) - + # Perform mutation included in DEX and crossover off = self.crossover(problem, parents, **kwargs) - + # Perform posterior mutation and repair if passed off = self.mutation(problem, off) - + return off - + class DE(GeneticAlgorithm): @@ -92,13 +91,13 @@ def __init__(self, ---------- 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' @@ -106,38 +105,38 @@ def __init__(self, - '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(). """ - + # Mating mating = VariantDE(variant=variant, CR=CR, @@ -145,7 +144,7 @@ def __init__(self, gamma=gamma, de_repair=de_repair, **kwargs) - + # Number of offsprings at each generation n_offsprings = pop_size @@ -163,13 +162,13 @@ def _initialize_advance(self, infills=None, **kwargs): self.pop = FitnessSurvival().do(self.problem, infills, n_survive=self.pop_size) def _infill(self): - + infills = self.mating(self.problem, self.pop, self.n_offsprings, algorithm=self) return infills def _advance(self, infills=None, **kwargs): - + assert infills is not None, "This algorithms uses the AskAndTell interface thus infills must be provided." # One-to-one replacement survival @@ -177,6 +176,6 @@ def _advance(self, infills=None, **kwargs): # Sort the population by fitness to make the selection simpler for mating (not an actual survival, just sorting) self.pop = FitnessSurvival().do(self.problem, self.pop) - + # Set ranks self.pop.set("rank", np.arange(self.pop_size)) diff --git a/pymoo/operators/crossover/dex.py b/pymoo/operators/crossover/dex.py index 67a487965..acecac7fb 100644 --- a/pymoo/operators/crossover/dex.py +++ b/pymoo/operators/crossover/dex.py @@ -10,7 +10,7 @@ # ========================================================================================================= class DEM(Crossover): - + def __init__(self, F=None, gamma=1e-4, @@ -21,55 +21,54 @@ def __init__(self, # Default value for F if F is None: F = (0.0, 1.0) - + # Define which method will be used to generate F values if hasattr(F, "__iter__"): self.scale_factor = self._randomize_scale_factor else: self.scale_factor = self._scalar_scale_factor - + # Define which method will be used to generate F values if not hasattr(de_repair, "__call__"): try: de_repair = REPAIRS[de_repair] except: raise KeyError("Repair must be either callable or in " + str(list(REPAIRS.keys()))) - + # Define which strategy of rotation will be used if gamma is None: self.get_diff = self._diff_simple else: self.get_diff = self._diff_jitter - + self.F = F self.gamma = gamma self.de_repair = de_repair - + super().__init__(1 + 2 * n_diffs, 1, prob=1.0, **kwargs) - - + def do(self, problem, pop, parents=None, **kwargs): - + # Convert pop if parents is not None if not parents is None: pop = pop[parents] - + # Get all X values for mutation parents Xr = np.swapaxes(pop, 0, 1).get("X") - + # Create mutation vectors V, diffs = self.de_mutation(Xr, return_differentials=True) # If the problem has boundaries to be considered if problem.has_bounds(): - + # Do repair V = self.de_repair(V, Xr[0], *problem.bounds()) - + return Population.new("X", V) - + def de_mutation(self, Xr, return_differentials=True): - + n_parents, n_matings, n_var = Xr.shape assert n_parents % 2 == 1, "For the differential an odd number of values need to be provided" @@ -86,42 +85,42 @@ def de_mutation(self, Xr, return_differentials=True): return V, diffs else: return V - + def _randomize_scale_factor(self, n_matings): - return (self.F[0] + np.random.random(n_matings) * (self.F[1] - self.F[0])) - + return (self.F[0] + np.random.random(n_matings) * (self.F[1] - self.F[0])) + def _scalar_scale_factor(self, n_matings): - return np.full(n_matings, self.F) - + return np.full(n_matings, self.F) + def _diff_jitter(self, F, Xi, Xj, n_matings, n_var): F = F[:, None] * (1 + self.gamma * (np.random.random((n_matings, n_var)) - 0.5)) return F * (Xi - Xj) - + def _diff_simple(self, F, Xi, Xj, n_matings, n_var): return F[:, None] * (Xi - Xj) - + def get_diffs(self, Xr, pairs, n_matings, n_var): - + # The differentials from each pair subtraction diffs = np.zeros((n_matings, n_var)) - + # For each difference for i, j in pairs: - + # Obtain F randomized in range F = self.scale_factor(n_matings) - + # New difference vector diff = self.get_diff(F, Xr[i], Xr[j], n_matings, n_var) - + # Add the difference to the first vector diffs = diffs + diff - + return diffs - - + + class DEX(Crossover): - + def __init__(self, variant="bin", CR=0.7, @@ -131,42 +130,41 @@ def __init__(self, at_least_once=True, de_repair="bounce-back", **kwargs): - + # Default value for F if F is None: F = (0.0, 1.0) - + # Create instace for mutation self.dem = DEM(F=F, gamma=gamma, de_repair=de_repair, n_diffs=n_diffs) - + self.CR = CR self.variant = variant self.at_least_once = at_least_once - + super().__init__(2 + 2 * n_diffs, 1, prob=1.0, **kwargs) - def do(self, problem, pop, parents=None, **kwargs): - + # Convert pop if parents is not None if not parents is None: pop = pop[parents] - + # Get all X values for mutation parents X = pop[:, 0].get("X") - - #About Xi + + # About Xi n_matings, n_var = X.shape - - #Obtain mutants + + # Obtain mutants mutants = self.dem.do(problem, pop[:, 1:], **kwargs) - + # Obtain V V = mutants.get("X") - + # Binomial crossover if self.variant == "bin": M = mut_binomial(n_matings, n_var, self.CR, at_least_once=self.at_least_once) @@ -180,9 +178,9 @@ def do(self, problem, pop, parents=None, **kwargs): X[M] = V[M] off = Population.new("X", X) - + return off - + def bounce_back(X, Xb, xl, xu): """Repair strategy @@ -196,7 +194,7 @@ def bounce_back(X, Xb, xl, xu): Returns: 2d array like: Repaired vectors. """ - + XL = xl[None, :].repeat(len(X), axis=0) XU = xu[None, :].repeat(len(X), axis=0) @@ -210,6 +208,7 @@ def bounce_back(X, Xb, xl, xu): return X + def midway(X, Xb, xl, xu): """Repair strategy @@ -222,7 +221,7 @@ def midway(X, Xb, xl, xu): Returns: 2d array like: Repaired vectors. """ - + XL = xl[None, :].repeat(len(X), axis=0) XU = xu[None, :].repeat(len(X), axis=0) @@ -236,6 +235,7 @@ def midway(X, Xb, xl, xu): return X + def to_bounds(X, Xb, xl, xu): """Repair strategy @@ -248,7 +248,7 @@ def to_bounds(X, Xb, xl, xu): Returns: 2d array like: Repaired vectors. """ - + XL = xl[None, :].repeat(len(X), axis=0) XU = xu[None, :].repeat(len(X), axis=0) @@ -262,6 +262,7 @@ def to_bounds(X, Xb, xl, xu): return X + def rand_init(X, Xb, xl, xu): """Repair strategy @@ -274,7 +275,7 @@ def rand_init(X, Xb, xl, xu): Returns: 2d array like: Repaired vectors. """ - + XL = xl[None, :].repeat(len(X), axis=0) XU = xu[None, :].repeat(len(X), axis=0) @@ -301,7 +302,7 @@ def squared_bounce_back(X, Xb, xl, xu): Returns: 2d array like: Repaired vectors. """ - + XL = xl[None, :].repeat(len(X), axis=0) XU = xu[None, :].repeat(len(X), axis=0) @@ -315,17 +316,19 @@ def squared_bounce_back(X, Xb, xl, xu): return X + def normalize_fun(fun): - + fmin = fun.min(axis=0) fmax = fun.max(axis=0) den = fmax - fmin - + den[den <= 1e-16] = 1.0 - + return (fun - fmin)/den -REPAIRS = {"bounce-back":bounce_back, - "midway":midway, - "rand-init":rand_init, - "to-bounds":to_bounds} + +REPAIRS = {"bounce-back": bounce_back, + "midway": midway, + "rand-init": rand_init, + "to-bounds": to_bounds} diff --git a/pymoo/operators/selection/des.py b/pymoo/operators/selection/des.py index 897eac412..d889f95fe 100644 --- a/pymoo/operators/selection/des.py +++ b/pymoo/operators/selection/des.py @@ -13,201 +13,203 @@ class DES(Selection): def __init__(self, variant, **kwargs): - + super().__init__() self.variant = variant def _do(self, problem, pop, n_select, n_parents, **kwargs): - + # Obtain number of elements in population n_pop = len(pop) - + # For most variants n_select must be equal to len(pop) variant = self.variant - + if variant == "ranked": """Proposed by Zhang et al. (2021). doi.org/10.1016/j.asoc.2021.107317""" P = self._ranked(pop, n_select, n_parents) - + elif variant == "best": P = self._best(pop, n_select, n_parents) - + elif variant == "current-to-best": P = self._current_to_best(pop, n_select, n_parents) - + elif variant == "current-to-rand": P = self._current_to_rand(pop, n_select, n_parents) - + else: P = self._rand(pop, n_select, n_parents) return P - + def _rand(self, pop, n_select, n_parents, **kwargs): - + # len of pop n_pop = len(pop) # Base form P = np.empty([n_select, n_parents], dtype=int) - + # Fill first column with corresponding parent P[:, 0] = np.arange(n_pop) # Fill next columns in loop for j in range(1, n_parents): - - P[:, j] = np.random.choice(n_pop, n_select) + + P[:, j] = np.random.choice(n_pop, n_select) reselect = (P[:, j].reshape([-1, 1]) == P[:, :j]).any(axis=1) - + while np.any(reselect): P[reselect, j] = np.random.choice(n_pop, reselect.sum()) reselect = (P[:, j].reshape([-1, 1]) == P[:, :j]).any(axis=1) - + return P - + def _best(self, pop, n_select, n_parents, **kwargs): - + # len of pop n_pop = len(pop) # Base form P = np.empty([n_select, n_parents], dtype=int) - + # Fill first column with corresponding parent P[:, 0] = np.arange(n_pop) - + # Fill first column with best candidate P[:, 1] = 0 # Fill next columns in loop for j in range(2, n_parents): - - P[:, j] = np.random.choice(n_pop, n_select) + + P[:, j] = np.random.choice(n_pop, n_select) reselect = (P[:, j].reshape([-1, 1]) == P[:, :j]).any(axis=1) - + while np.any(reselect): P[reselect, j] = np.random.choice(n_pop, reselect.sum()) reselect = (P[:, j].reshape([-1, 1]) == P[:, :j]).any(axis=1) - + return P - + def _current_to_best(self, pop, n_select, n_parents, **kwargs): - + # len of pop n_pop = len(pop) # Base form P = np.empty([n_select, n_parents], dtype=int) - + # Fill first column with corresponding parent P[:, 0] = np.arange(n_pop) - + # Fill first column with current candidate P[:, 1] = np.arange(n_pop) - + # Fill first direction from current P[:, 3] = np.arange(n_pop) - + # Towards best P[:, 2] = 0 # Fill next columns in loop for j in range(4, n_parents): - - P[:, j] = np.random.choice(n_pop, n_select) + + P[:, j] = np.random.choice(n_pop, n_select) reselect = (P[:, j].reshape([-1, 1]) == P[:, :j]).any(axis=1) - + while np.any(reselect): P[reselect, j] = np.random.choice(n_pop, reselect.sum()) reselect = (P[:, j].reshape([-1, 1]) == P[:, :j]).any(axis=1) - + return P - + def _current_to_rand(self, pop, n_select, n_parents, **kwargs): - + # len of pop n_pop = len(pop) # Base form P = np.empty([n_select, n_parents], dtype=int) - + # Fill first column with corresponding parent P[:, 0] = np.arange(n_pop) - + # Fill first column with current candidate P[:, 1] = np.arange(n_pop) - + # Fill first direction from current P[:, 3] = np.arange(n_pop) - + # Towards random - P[:, 2] = np.random.choice(n_pop, n_select) + P[:, 2] = np.random.choice(n_pop, n_select) reselect = (P[:, 2].reshape([-1, 1]) == P[:, [0, 1, 3]]).any(axis=1) - + while np.any(reselect): P[reselect, 2] = np.random.choice(n_pop, reselect.sum()) reselect = (P[:, 2].reshape([-1, 1]) == P[:, [0, 1, 3]]).any(axis=1) # Fill next columns in loop for j in range(4, n_parents): - - P[:, j] = np.random.choice(n_pop, n_select) + + P[:, j] = np.random.choice(n_pop, n_select) reselect = (P[:, j].reshape([-1, 1]) == P[:, :j]).any(axis=1) - + while np.any(reselect): P[reselect, j] = np.random.choice(n_pop, reselect.sum()) reselect = (P[:, j].reshape([-1, 1]) == P[:, :j]).any(axis=1) - + return P - + def _ranked(self, pop, n_select, n_parents, **kwargs): - + P = self._rand(pop, n_select, n_parents, **kwargs) P[:, 1:] = rank_sort(P[:, 1:], pop) - + return P - + def ranks_from_cv(pop): - + ranks = pop.get("rank") cv_elements = ranks == None - + if np.any(cv_elements): ranks[cv_elements] = np.arange(len(pop))[cv_elements] - + return ranks + def rank_sort(P, pop): - + ranks = ranks_from_cv(pop) - - sorted = np.argsort(ranks[P], axis=1, kind="stable") + + sorted = np.argsort(ranks[P], axis=1, kind="stable") S = np.take_along_axis(P, sorted, axis=1) P[:, 0] = S[:, 0] - + n_diffs = int((P.shape[1] - 1) / 2) for j in range(1, n_diffs + 1): P[:, 2*j - 1] = S[:, j] P[:, 2*j] = S[:, -j] - + return P + def reiforce_directions(P, pop): - + ranks = ranks_from_cv(pop) - - ranks = ranks[P] + + ranks = ranks[P] S = P.copy() - + n_diffs = int(P.shape[1] / 2) for j in range(0, n_diffs): bad_directions = ranks[:, 2*j] > ranks[:, 2*j + 1] P[bad_directions, 2*j] = S[bad_directions, 2*j + 1] P[bad_directions, 2*j + 1] = S[bad_directions, 2*j] - + return P