├── README.md ├── evolution_lib.py ├── evolution_search_nsga.py └── function_utils.py /README.md: -------------------------------------------------------------------------------- 1 | # evolutionary_algorithm_NSGA-II 2 | The multiobjective evolutionary algorithm NSGA-II implementation in Python. 3 | 4 | `evolution_search_nsga.py` Main program, save the population scatter in each generation. 5 | `evolution_lib.py` Implement of algorithm. 6 | `function_utils.py` Define the test function. 7 | 8 | Refering to *A fast and elitist multiobjective genetic algorithm: NSGA-II* . 9 | -------------------------------------------------------------------------------- /evolution_lib.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import random 3 | from function_utils import * 4 | from evolution_search_nsga import parameter_lower_bound,parameter_upper_bound 5 | 6 | class individual(object): 7 | 8 | def __init__(self,temp): 9 | self.parameter = temp[:] 10 | self.target = get_target(self.parameter) 11 | self.violation = get_violation(self.parameter) 12 | self.front_rank = 0 13 | self.domination_counter = 0 14 | self.crowding_distance = 0 15 | self.set = set() 16 | 17 | def show(self): 18 | print self.parameter,self.target 19 | 20 | 21 | def get_target(parameter): 22 | return Binh_and_Korn(parameter) 23 | 24 | 25 | def get_violation(parameter): 26 | return Binh_and_Korn_constraints(parameter) 27 | 28 | 29 | def initial_population(p_size,para_num,lower_bound,upper_bound): 30 | population = [] 31 | for i in range(0,p_size): 32 | temp = [] 33 | for j in range(0,para_num): 34 | lb = lower_bound[j] 35 | ub = upper_bound[j] 36 | temp.append(random.uniform(lb,ub)) 37 | ind = individual(temp) 38 | population.append(ind) 39 | return set(population) 40 | 41 | 42 | def is_dominate(p,q): 43 | dominate = False 44 | 45 | for i in range(0,len(p.target)): 46 | if p.target[i] > q.target[i] : 47 | return False 48 | if p.target[i] < q.target[i] : 49 | dominate = True 50 | 51 | for i in range(0,len(p.violation)): 52 | if p.violation[i] > q.violation[i] : 53 | return False 54 | if p.violation[i] < q.violation[i] : 55 | dominate = True 56 | 57 | return dominate 58 | 59 | 60 | def fast_non_dominated_sort(population): 61 | f_set = set() 62 | rank = 1 63 | for p in population: 64 | for q in population: 65 | if p is q : 66 | continue 67 | if is_dominate(p,q): 68 | p.set.add(q) 69 | elif is_dominate(q,p): 70 | p.domination_counter = p.domination_counter + 1 71 | if p.domination_counter == 0 : 72 | p.front_rank = rank 73 | f_set.add(p) 74 | 75 | while not len(f_set)==0 : 76 | rank = rank + 1 77 | temp_set = set() 78 | for p in f_set : 79 | for q in p.set : 80 | q.domination_counter = q.domination_counter - 1 81 | if q.domination_counter==0 and q.front_rank==0 : 82 | q.front_rank = rank 83 | temp_set.add(q) 84 | f_set = temp_set 85 | 86 | 87 | def calculate_crowd_dis(population,parameter_num): 88 | infinite = 100000.0 # a large number as infinte 89 | 90 | for dim in range(0,parameter_num): 91 | new_list = sort_by_coordinate(population,dim) 92 | 93 | new_list[0].crowding_distance += infinite 94 | new_list[-1].crowding_distance += infinite 95 | max_distance = new_list[0].parameter[dim] - new_list[-1].parameter[dim] 96 | for i in range(1,len(new_list)-1): 97 | distance = new_list[i-1].parameter[dim] - new_list[i+1].parameter[dim] 98 | if max_distance == 0 : 99 | new_list[i].crowding_distance = 0 100 | else : 101 | new_list[i].crowding_distance += distance/max_distance 102 | 103 | for p in population : 104 | p.crowding_distance = p.crowding_distance/parameter_num 105 | 106 | 107 | def sort_by_coordinate(population,dim): # selection sort, which can be replaced with quick sort 108 | p_list = [] 109 | for p in population: 110 | p_list.append(p) 111 | 112 | for i in range(0,len(p_list)-1): 113 | for j in range(i+1,len(p_list)): 114 | if p_list[i].parameter[dim] < p_list[j].parameter[dim]: 115 | temp = p_list[i] 116 | p_list[i] = p_list[j] 117 | p_list[j] = temp 118 | 119 | return p_list 120 | 121 | 122 | def tournment_select(prarents,part_num=2): # binary tournment selection 123 | 124 | participants = random.sample(prarents, part_num) 125 | best = participants[0] 126 | best_rank = participants[0].front_rank 127 | best_crowding_distance = participants[0].crowding_distance 128 | 129 | for p in participants[1:] : 130 | if p.front_rank < best_rank or \ 131 | (p.front_rank == best_rank and p.crowding_distance > best_crowding_distance): 132 | best = p 133 | best_rank = p.front_rank 134 | best_crowding_distance = p.crowding_distance 135 | 136 | return best 137 | 138 | 139 | def genarate(p_size,prarents,cross_prob,mutation_prob): 140 | # generate two children from two parents 141 | 142 | children = set() 143 | while len(children) < p_size: 144 | parent1 = tournment_select(prarents) 145 | parent2 = tournment_select(prarents) 146 | while parent1 == parent2 : 147 | parent2 = tournment_select(prarents) 148 | 149 | child1,child2 = cross(parent1,parent2,cross_prob) 150 | child1 = mutation(child1,mutation_prob) 151 | child2 = mutation(child2,mutation_prob) 152 | 153 | children.add(child1) 154 | children.add(child2) 155 | return children 156 | 157 | 158 | def cross(p1,p2,prob): # the random linear operator 159 | if random.uniform(0,1) >= prob: 160 | return p1,p2 161 | 162 | parameter1,parameter2 = [],[] 163 | linear_range = 2 164 | alpha = random.uniform(0,linear_range) 165 | for j in range(0,len(p1.parameter)): 166 | parameter1.append(alpha*p1.parameter[j] + 167 | (1-alpha)*p2.parameter[j] ) 168 | parameter2.append((1-alpha)*p1.parameter[j] + 169 | alpha*p2.parameter[j] ) 170 | c1 = individual(parameter1) 171 | c2 = individual(parameter2) 172 | return c1,c2 173 | 174 | 175 | def mutation(p,prob): # uniform random mutation 176 | 177 | mutation_space = 0.1 178 | parameter = [] 179 | for i in range(0,len(p.parameter)): 180 | if random.uniform(0,1) < prob: 181 | para_range = mutation_space*(parameter_upper_bound[i]-parameter_lower_bound[i]) 182 | mutation = random.uniform(-para_range,para_range) 183 | parameter.append(p.parameter[i]+mutation) 184 | else : 185 | parameter.append(p.parameter[i]) 186 | 187 | p_new = individual(parameter) 188 | return p_new 189 | -------------------------------------------------------------------------------- /evolution_search_nsga.py: -------------------------------------------------------------------------------- 1 | import random 2 | from evolution_lib import * 3 | import matplotlib.pyplot as plt 4 | 5 | p_size = 200 # population size 6 | parameter_num = 2 # dimension of parameters to be optimized 7 | target_num = 2 # dimension of target function 8 | parameter_lower_bound = [ 0, 0 ] 9 | parameter_upper_bound = [ 5, 3 ] 10 | 11 | iteration = 100 12 | prob_cross = 0.6 13 | prob_mutaion = 0.05 14 | 15 | if __name__ == '__main__': 16 | 17 | parents = initial_population(p_size,parameter_num, 18 | parameter_lower_bound,parameter_upper_bound) 19 | children = set() 20 | 21 | for it in range(1,iteration+1): 22 | 23 | all_population = parents | children 24 | fast_non_dominated_sort(all_population) 25 | calculate_crowd_dis(all_population,parameter_num) 26 | 27 | parents = set() 28 | front = 1 29 | while len(parents) < p_size: 30 | for ind in all_population: 31 | if ind.front_rank == front: 32 | parents.add(ind) 33 | if len(parents) == p_size : 34 | break 35 | front = front + 1 36 | 37 | # draw the scatter diagram of population 38 | X,Y,C = [],[],[] 39 | for p in parents: 40 | if True: #p.front_rank == 1 : 41 | X.append(p.target[0]) 42 | Y.append(p.target[1]) 43 | C.append(p.front_rank) 44 | plt.figure() 45 | plt.scatter(X,Y, s=10, c=C) 46 | plt.xlim(0,200), plt.xticks([]) 47 | plt.ylim(0,80), plt.yticks([]) 48 | plt.savefig('F:/scatter_'+str(it)+'.png',dpi=48) 49 | 50 | # calculate the indicator of population 51 | best = [float('inf') for i in range(0,target_num)] 52 | mean = [0 for i in range(0,target_num)] 53 | for p in parents: 54 | for i in range(0,target_num): 55 | if p.target[i] < best[i]: 56 | best[i] = p.target[i] 57 | mean[i] = mean[i] + p.target[i] 58 | for i in range(0,target_num): 59 | mean[i] = mean[i]/p_size 60 | 61 | print 'the',it,'th generation' 62 | print 'best_target:',best,' mean_target:',mean 63 | 64 | children = genarate(p_size,parents,prob_cross,prob_mutaion) 65 | 66 | -------------------------------------------------------------------------------- /function_utils.py: -------------------------------------------------------------------------------- 1 | import math 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | from mpl_toolkits.mplot3d import Axes3D 5 | 6 | def Fonseca(x_list): 7 | # -4 <= xi <= 4 8 | s1,s2 = 0.0,0.0 9 | n = len(x_list) 10 | for x in x_list : 11 | s1 = s1 + pow(x-1.0/math.sqrt(float(n)),2.0) 12 | s2 = s2 + pow(x+1.0/math.sqrt(float(n)),2.0) 13 | 14 | target = [] 15 | target.append(1.0 - math.exp(-s1)) 16 | target.append(1.0 - math.exp(-s2)) 17 | return target 18 | 19 | def Kursawe(x_list): 20 | # -5 <= xi <= 5, i = 3 21 | f1,f2 = 0.0,0.0 22 | for i in range(0,2) : 23 | f1 = f1 - 10*math.exp(-0.2*math.sqrt(pow(x_list[i],2.0)+pow(x_list[i+1],2.0))) 24 | for i in range(0,3) : 25 | f2 = f2 + pow(abs(x_list[i]),0.8) + 5*math.sin(pow(x_list[i],3.0)) 26 | target = [f1,f2] 27 | return target 28 | 29 | def Binh_and_Korn(x_list): 30 | # 0 <= x <=5, 0 <= y <= 3 31 | x = x_list[0] 32 | y = x_list[1] 33 | f1 = 4*pow(x,2) + 4*pow(y,2) 34 | f2 = pow(x-5,2)+pow(y-5,2) 35 | target = [f1,f2] 36 | return target 37 | 38 | def Binh_and_Korn_constraints(x_list): 39 | # 0 <= x <=5, 0 <= y <= 3 40 | x = x_list[0] 41 | y = x_list[1] 42 | g1 = max(0, 25 - pow(x-5,2) - pow(y,2)) 43 | g2 = max(0, pow(x-8,2)+pow(y+3,2) - 7.7) 44 | violation = [g1,g2] 45 | return violation 46 | 47 | def Griewank(x_list): 48 | # -300 <= xi <= 300 49 | # global minimal is at [0,0,..] where value = 0 50 | x1 = 0.0 51 | x2 = 1.0 52 | for i,x in enumerate(x_list,1): 53 | x1 = x1 + x*x 54 | x2 = x2 * math.cos(x/math.sqrt(i)) 55 | return x1/4000 - x2 + 1 56 | 57 | def Rastrigrin(x_list): 58 | # -30 <= xi <= 30 59 | # global minimal is at [0,0,..] where value = 0 60 | x1 = 0.0 61 | for i,x in enumerate(x_list,1): 62 | x1 = x1 + x*x - 10*math.cos(2*math.pi*x) + 10 63 | return x1 64 | 65 | def Rosenbrock(x_list): 66 | # -30 <= xi <= 30 67 | # global minimal is at [1,1,..] where value = 0 68 | x1 = 0.0 69 | for i in range(0,len(x_list)-1): 70 | x1 = x1 + 100*(x_list[i]*x_list[i]-x_list[i+1])*(x_list[i]*x_list[i]-x_list[i+1]) +\ 71 | (1-x_list[i])*(1-x_list[i]) 72 | return x1 73 | 74 | def Ackley(x_list): 75 | # -5 <= xi <= 5 76 | # global minimal is at [0,0,..] where value = 0 77 | x1 = 0.0 78 | x2 = 0.0 79 | for i,x in enumerate(x_list,1): 80 | x1 = x1 + x*x 81 | x2 = x2 + math.cos(2*math.pi*x) 82 | x1 = -0.2*math.sqrt(x1/i) 83 | x2 = x2/i 84 | return -20*math.pow(math.e,x1) - math.pow(math.e,x2) + 22.7182818285 85 | 86 | if __name__ == '__main__': 87 | 88 | print Ackley([0,0]) 89 | 90 | fig = plt.figure() 91 | ax = Axes3D(fig) 92 | X = np.arange(-5, 5, 0.02) 93 | Y = np.arange(-5, 5, 0.02) 94 | Z = [ ([0]*len(Y)) for i in range(len(X)) ] # x rows y columns 95 | X, Y = np.meshgrid(X, Y) 96 | 97 | for i in range(0,len(Z)): 98 | for j in range(0,len(Z[0])): 99 | Z[i][j] = Ackley([X[i][j],Y[i][j]]) 100 | 101 | ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=plt.cm.coolwarm,linewidth=2) 102 | plt.show() 103 | 104 | --------------------------------------------------------------------------------