Skip to content

Commit

Permalink
Some minor modifications
Browse files Browse the repository at this point in the history
- Modified the data dictionary of algorithm
- Termination criteria can be now an object as well and is stored in the algorithm
- setup_ext.py is now used for compiling
  • Loading branch information
blankjul committed Nov 13, 2018
1 parent a94bdba commit 445b947
Show file tree
Hide file tree
Showing 13 changed files with 257 additions and 142 deletions.
64 changes: 23 additions & 41 deletions pymoo/algorithms/genetic_algorithm.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from scipy.spatial.distance import cdist

from pymoo.model.algorithm import Algorithm
from pymoo.model.evaluator import Evaluator
from pymoo.model.individual import Individual
from pymoo.model.population import Population
from pymoo.rand import random
Expand Down Expand Up @@ -98,44 +97,33 @@ def __init__(self,
if self.n_offsprings is None:
self.n_offsprings = pop_size

# all run specific data are stored in this dictionary - (is sharing in all modules, e.g. sampling, mutation,..)
self.data = {}

# other run specific data updated whenever solve is called - to share them in all methods
self.evaluator = None
self.problem = None
self.n_gen = None
self.pop = None
self.off = None

def _solve(self, problem, termination):

# the evaluator object which is counting the evaluations
self.evaluator = Evaluator()
self.problem = problem

# generation counter
n_gen = 1

# always create a new function evaluator which is counting the evaluations
self.data = {**self.data, 'problem': problem, 'evaluator': self.evaluator,
'termination': termination, 'n_gen': n_gen}
self.n_gen = 1

# initialize the first population and evaluate it
pop = self._initialize()
self._each_iteration(self.data, first=True)
self.pop = self._initialize()
self._each_iteration(self, first=True)

# while termination criterium not fulfilled
while termination.do_continue(self.data):
self.data['n_gen'] += 1
while termination.do_continue(self):
self.n_gen += 1

# do the next iteration
pop = self._next(pop)
self.data = {**self.data, 'pop': pop}
self.pop = self._next(self.pop)

# execute the callback function in the end of each generation
self._each_iteration(self.data)
self._each_iteration(self)

self._finalize()

return pop
return self.pop

def _initialize(self):
# ! get the initial population - different ways are possible
Expand All @@ -148,37 +136,31 @@ def _initialize(self):
if isinstance(self.sampling, np.ndarray):
pop = pop.new("X", self.sampling)
else:
pop = self.sampling.sample(self.problem, pop, self.pop_size, D=self.data)

# add to the data dictionary to be used in all modules - allows access to pop over data object
self.data = {**self.data, 'pop': pop}
pop = self.sampling.sample(self.problem, pop, self.pop_size, algorithm=self)

# in case the initial population was not evaluated
if np.any(pop.collect(lambda ind: ind.F is None, as_numpy_array=True)):
self.evaluator.eval(self.problem, pop, D=self.data)
self.evaluator.eval(self.problem, pop, algorithm=self)

# that call is a dummy survival to set attributes that are necessary for the mating selection
if self.survival:
pop = self.survival.do(self.problem, pop, self.pop_size, D=self.data)
pop = self.survival.do(self.problem, pop, self.pop_size, algorithm=self)

return pop

def _next(self, pop):

# do the mating using the current population
off = self._mating(pop)
self.off = self._mating(pop)

# evaluate the offspring
self.evaluator.eval(self.problem, off, D=self.data)

# add to the data dictionary of algorithms
self.data = {**self.data, 'off': off}
self.evaluator.eval(self.problem, self.off, algorithm=self)

# merge the offsprings with the current population
pop = pop.merge(off)
pop = pop.merge(self.off)

# the do survival selection
pop = self.survival.do(self.problem, pop, self.pop_size, D=self.data)
pop = self.survival.do(self.problem, pop, self.pop_size, algorithm=self)

return pop

Expand All @@ -197,17 +179,17 @@ def _mating(self, pop):
n_select = math.ceil((self.n_offsprings - len(off)) / self.crossover.n_offsprings)

# select the parents for the mating - just an index array
parents = self.selection.do(pop, n_select, self.crossover.n_parents, D=self.data)
parents = self.selection.do(pop, n_select, self.crossover.n_parents, algorithm=self)

# do the crossover using the parents index and the population - additional data provided if necessary
_off = self.crossover.do(self.problem, pop, parents, D=self.data)
_off = self.crossover.do(self.problem, pop, parents, algorithm=self)

# do the mutation on the offsprings created through crossover
_off = self.mutation.do(self.problem, _off, D=self.data)
_off = self.mutation.do(self.problem, _off, algorithm=self)

# repair the individuals if necessary
if self.func_repair is not None:
self.func_repair(self.problem, _off, D=self.data)
self.func_repair(self.problem, _off, algorithm=self)

if self.eliminate_duplicates:
# eliminate duplicates if too close to the current population or already recombined individuals
Expand All @@ -229,7 +211,7 @@ def _mating(self, pop):

# if no new offsprings can be generated within 100 trails -> return the current result
if n_matings > 100:
self.data['termination'].flag = False
self.termination.flag = False
break

return off
Expand Down
6 changes: 3 additions & 3 deletions pymoo/algorithms/nsga2.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,15 @@ def __init__(self, pop_size=100, **kwargs):

super().__init__(**kwargs)

self.data['tournament_type'] = 'comp_by_dom_and_crowding'
self.tournament_type = 'comp_by_dom_and_crowding'
self.func_display_attrs = disp_multi_objective


def binary_tournament(pop, P, D, **kwargs):
def binary_tournament(pop, P, algorithm, **kwargs):
if P.shape[1] != 2:
raise ValueError("Only implemented for binary tournament!")

tournament_type = D['tournament_type']
tournament_type = algorithm.tournament_type
S = np.full(P.shape[0], np.nan)

for i in range(P.shape[0]):
Expand Down
2 changes: 2 additions & 0 deletions pymoo/algorithms/rnsga3.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ def __init__(self,
mu=0.05,
**kwargs):

print("WARNING: There is still a bug here. Will be fixed soon!")

n_obj = ref_points.shape[1]
n_ref_points = ref_points.shape[0]

Expand Down
11 changes: 5 additions & 6 deletions pymoo/algorithms/so_de.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,17 @@ def _next(self, pop):
else:
raise Exception("Unknown selection: %s" % self.var_selection)

off = self.crossover.do(self.problem, pop, P)
self.data = {**self.data, "off": off}
self.off = self.crossover.do(self.problem, pop, P)

# do the mutation by using the offsprings
off = self.mutation.do(self.problem, pop, D=self.data)
self.off = self.mutation.do(self.problem, pop, algorithm=self)

# evaluate the results
self.evaluator.eval(self.problem, off, D=self.data)
self.evaluator.eval(self.problem, self.off, algorithm=self)

# replace whenever offspring is better than population member
for i in range(len(pop)):
if off[i].F < pop[i].F:
pop[i] = off[i]
if self.off[i].F < pop[i].F:
pop[i] = self.off[i]

return pop
25 changes: 17 additions & 8 deletions pymoo/model/algorithm.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from abc import abstractmethod

import numpy as np
from pymoo.model.evaluator import Evaluator

from pymoo.model.result import Result
from pymoo.model.termination import MaximumFunctionCallTermination
Expand All @@ -21,12 +22,16 @@ class Algorithm:

def __init__(self, **kwargs) -> None:
super().__init__()
self.evaluator = None
self.problem = None
self.termination = None
self.pf = None

self.disp = None
self.func_display_attrs = None
self.callback = None
self.history = []
self.save_history = None
self.pf = None

def solve(self,
problem,
Expand Down Expand Up @@ -82,13 +87,15 @@ def callback(algorithm):
# set the random seed for generator
random.seed(seed)

# the evaluator object which is counting the evaluations
self.evaluator = Evaluator()
self.problem = problem
self.termination = termination
self.pf = pf

self.disp = disp
self.callback = callback
self.save_history = save_history
self.pf = pf

if isinstance(termination, int):
termination = MaximumFunctionCallTermination(termination)

# call the algorithm to solve the problem
pop = self._solve(problem, termination)
Expand Down Expand Up @@ -127,20 +134,22 @@ def _each_iteration(self, D, first=False, **kwargs):

# display the output if defined by the algorithm
if self.disp and self.func_display_attrs is not None:
disp = self.func_display_attrs(D['problem'], D['evaluator'], D, self.pf)
disp = self.func_display_attrs(self.problem, self.evaluator, self, self.pf)
if disp is not None:
self._display(disp, header=first)

# if a callback function is provided it is called after each iteration
if self.callback is not None:
# use the callback here without having the function itself
self.callback(self)

if self.save_history:
hist = self.history
self.history = None
hist, _callback = self.history, self.callback
self.history, self.callback = None, None

obj = copy.deepcopy(self)
self.history = hist
self.callback = _callback

self.history.append(obj)

Expand Down
50 changes: 38 additions & 12 deletions pymoo/model/termination.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ def __init__(self, n_max_evals) -> None:
super().__init__()
self.n_max_evals = n_max_evals

def _do_continue(self, D):
return D['evaluator'].n_eval < self.n_max_evals
def _do_continue(self, algorithm):
return algorithm.evaluator.n_eval < self.n_max_evals


class MaximumGenerationTermination(Termination):
Expand All @@ -30,22 +30,48 @@ def __init__(self, n_max_gen) -> None:
super().__init__()
self.n_max_gen = n_max_gen

def _do_continue(self, D):
return D['n_gen'] < self.n_max_gen
def _do_continue(self, algorithm):
return algorithm.n_gen < self.n_max_gen


class IGDTermination(Termination):

def __init__(self, problem, igd) -> None:
def __init__(self, min_igd, pf) -> None:
super().__init__()
pf = problem.pareto_front()

if pf is None:
raise Exception("You can only use IGD termination criteria if the pareto front is known!")

self.obj = IGD(pf)
self.igd = igd

def _do_continue(self, D):
return self.obj.calc(D['pop'].F) > self.igd

self.igd = min_igd

def _do_continue(self, algorithm):
F = algorithm.pop.get("F")
return self.obj.calc(F) > self.igd


def get_termination(_type, *args, pf=None):
"""
Parameters
----------
_type : str
Type of termination as string
args : list
List of arguments for the termination object
pf : np.array
The pareto-front if it is known. Might be necessary for some termination criteria.
Returns
-------
The termination object to be used in the algorithm.
"""
if _type == 'n_eval':
termination = MaximumFunctionCallTermination(*args)
elif _type == 'n_gen':
termination = MaximumGenerationTermination(*args)
elif _type == 'igd':
termination = IGDTermination(*args, pf=pf)
else:
raise Exception('Unknown Termination criterium: %s' % _type)
return termination
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import numpy as np

from pymoo.model.mutation import Mutation
from pymoo.rand import random

import numpy as np


class DifferentialEvolutionMutation(Mutation):
def __init__(self, variant, CR):
super().__init__(True)
self.CR = CR
self.variant = variant

def _do(self, problem, pop, D=None, **kwargs):
def _do(self, problem, pop, algorithm, **kwargs):

X = pop.get("X")

off = D['off']
off = algorithm.off
_X = off.get("X")

# do the crossover
Expand Down
15 changes: 4 additions & 11 deletions pymoo/optimize.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from pymoo.model.termination import MaximumFunctionCallTermination, MaximumGenerationTermination, IGDTermination
from pymoo.model.termination import MaximumFunctionCallTermination, MaximumGenerationTermination, IGDTermination, \
Termination, get_termination
from pymoo.rand import random


def get_alorithm(name):

if name == 'ga':
from pymoo.algorithms.so_genetic_algorithm import SingleObjectiveGeneticAlgorithm
return SingleObjectiveGeneticAlgorithm
Expand Down Expand Up @@ -64,15 +64,8 @@ def minimize(problem,
"""

# create an evaluator defined by the termination criterium
termination_criterium, termination_val = termination
if termination_criterium == 'n_eval':
termination = MaximumFunctionCallTermination(termination_val)
elif termination_criterium == 'n_gen':
termination = MaximumGenerationTermination(termination_val)
elif termination_criterium == 'igd':
termination = IGDTermination(problem, termination_val)
else:
raise Exception('Unknown Termination criterium: %s' % termination_criterium)
if not isinstance(termination, Termination):
termination = get_termination(*termination, pf=kwargs.get('pf', None))

# set a random random seed if not provided
if 'seed' not in kwargs:
Expand Down
Loading

0 comments on commit 445b947

Please sign in to comment.