import math import random import whrandom import sys def convtoint(f): return math.floor(f) def convtonear1(f): return 1-1.0/f def convtorange(f,lo,up): return max(lo,min(up,f)) def convtopos(f): return max(0,f) def list_to_tuple(l): t=() for i in l: t=t+(i,) return t class individual: def __init__(self, x, d, dd, Fitness=-1e37, Birthday=-1): self.x = x # parameter vector characterizing the individual self.fitness = Fitness # fitness of the individual self.birthday = Birthday # birthday (used for selection by age) self.d = d # distortion vectors self.dd = dd def __cmp__(self,other): return -cmp(self.fitness,other.fitness) def __repr__(self): return "individual(%s,%s,%s,%s,%s)" % (self.x,self.d, self.dd,self.fitness, self.birthday) class evol: def __init__(self, n, objective_function, startx=0, startd=0, pop=20, child=10, life_span = 5, gen=100, matestrat=0, startdd=0.5, output=sys.stdout): """Constructor, that does the optimization. Best individual is stored in member 'self.best'.""" # default: set all parameters to zero if not startx: startx = n*[0.] # default: assume a distortion ratio of 10% if not startd: startd = map(lambda a: a/10. + 0.2, startx) self.mate_strategy = matestrat self.generation_no = 0 self.objective_function = objective_function self.population_size = pop self.individuals = [] # initial best individual self.best = individual(startx, startd, startdd) self.integrate_new_member(self.best) # create initial population for i in range(self.population_size): ancestor = self.mutate(self.best) self.integrate_new_member(ancestor) if type(output) == type(""): filehandle = open(output, "w") else: filehandle = output # loop over number of generations for self.generation_no in range(gen): # create new childs from population (through mating) for i in range(child): mom, daddy = self.find_mom_and_daddy() kid = self.mate(daddy, mom) kid = self.mutate(kid) # welcome the new member in the population pool self.integrate_new_member(kid) # select the best individuals self.selection() # test 'age' of individuals, kill individual if too old, i.e. # birthday(individual) > birthday limit if len(self.individuals) > self.population_size: self.individuals = filter(lambda i, birthday_limit = self.generation_no - life_span: i.birthday > birthday_limit, self.individuals) if output: # output the progress of the optimization filehandle.write("%i %.24f " % (self.generation_no, self.best.fitness)) for value in self.best.x: filehandle.write("%.10f " % value) filehandle.write("\n") filehandle.flush() if len(self.individuals) == 0: print "\aerror in generation %i: population size decreased to zero !" % \ self.generation_no break def find_mom_and_daddy(self): """Finds two different individuals that serve as mom and daddy for the creation of a new individual.""" daddy = whrandom.choice(self.individuals) mom = whrandom.choice(self.individuals) # mom can't be equalt to daddy => search another mom if mom.x == daddy.x: individuals_copy=self.individuals[:] while mom.x == daddy.x and len(individuals_copy)>1: individuals_copy.remove(mom) mom = whrandom.choice(individuals_copy) # if mom.x == daddy.x: # self.print_individuals() return mom, daddy def mate(self, mom, daddy): """Create a new individual consisting of a parameter vector combined of 'mom' and 'daddy'.""" x = self.combine_vectors(daddy.x, mom.x) d = self.combine_vectors(daddy.d, mom.d) dd = self.combine_elements(daddy.dd, mom.dd) return individual(x, d, dd) def selection(self): """Select the best individuals of a given population. Number of selected individuals is specified by 'self.population_size'.""" if len(self.individuals) < self.population_size: return # sort list of individuals correspondent their fitness self.individuals.sort() # forget about the worst losers del self.individuals[self.population_size:] def combine_vectors(self, vec_a, vec_b): """Combine two vectors elementwise.""" return map(lambda i, j, ce=self.combine_elements: ce(i, j), vec_a, vec_b) def combine_elements(self, el_a, el_b): """Having two elements, combine them based on 'mating strategy'.""" if self.mate_strategy: return (el_a + el_b)/2. else: return whrandom.choice([el_a, el_b]) def integrate_new_member(self, baby): """Brand individual with: 'fitness' and 'birthday'. Include it into the population.""" baby.fitness = apply(self.objective_function, list_to_tuple(baby.x)) baby.birthday = self.generation_no if baby.fitness > self.best.fitness: self.best = baby # append it to the list of individuals self.individuals.append(baby) def mutate(self, i): """Add some noise to a parameter setting of an individual.""" # Here is something strange. In which order should the dd and d # be actualised? I don't know. Does it matter? I dont' know? # compute the deviation dd = i.dd * random.normalvariate(1,0.1) alpha = random.normalvariate(0, dd) d = map(lambda dev, dd=i.dd, alpha=alpha: dev * math.exp(alpha + random.normalvariate(0., dd)), i.d) # d is not allowed to be to low d = map(lambda dev,x: max(dev,abs(x*1e-10)), d,i.x) # distort all parameters in parameter vector x = map(lambda x, dx: random.normalvariate(x, dx), i.x, i.d) # set the distorted values return individual(x, d, dd) def print_individuals(self): import string print string.join(map(lambda a:`a`+"\n",self.individuals)) def print_best(self): print self.best