-
Notifications
You must be signed in to change notification settings - Fork 0
/
genetic_algorithm.py
194 lines (125 loc) · 5.39 KB
/
genetic_algorithm.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
import numpy as np
import pandas as pd
import random
from random import choices
import math
from operator import itemgetter
import itertools
def read_data(filename):
data = []
with open(filename, 'r') as f:
for l in f.readlines():
l = l.rstrip().split()
l = [int(coord) for coord in l]
data.append(l)
return data[0][0], data[1:]
def create_initial_pop(size, cities):
# randomly generate the intial population
initial_pop = [np.random.permutation(cities).tolist() for _ in range(size)]
return initial_pop
def fitness(path): # the higher the more optimal
# Input:
# path: a list of numpy vectors representing coordinates
# Output: the inverse of Eucleadian distance covered visiting the coordinates sequentially
distance = 0
last_stop = len(path) - 1
for i in range(last_stop):
distance += np.linalg.norm(np.array(path[i+1]) - np.array(path[i]))
# back to the starting point
distance += np.linalg.norm(np.array(path[0]) - np.array(path[last_stop]))
return 1/distance
def rank_fitness(population):
# Takes a list of paths and return a sorted list based on fitness scores
rank_list = []
for i, individual in enumerate(population):
fit_score = fitness(individual)
rank_list.append((i, fit_score))
rank_list.sort(key=itemgetter(1), reverse=True)
return rank_list # sorted(rank_list, key = lambda x: x[1], reverse=True)
def select_parents(size, population, rank_list, top_k):
# rank_list: a list of tuples of index and fitness scores sorted in descending order.
# top_k: the best k routes we will retain unconditionally
elite_parents = []
for i in range(top_k):
elite_parents.append(population[rank_list[i][0]])
# get all combinations of the elite parents
parents_list = [list(parents) for parents in list(itertools.combinations(elite_parents, 2))]
# generate probability distribution for each fitness score
denomitor = sum(score for i, score in rank_list)
ranked_probs = [(i, score/denomitor) for i, score in rank_list]
locs = list(map(lambda x: population[x[0]], ranked_probs))
probs = list(map(lambda x: x[1], ranked_probs))
# run the Roulette Wheel
cnt = size - len(parents_list)
while cnt <= size:
parents = choices(locs, weights = probs, k=2)
parents_list.append(parents)
cnt += 2
return parents_list
def resolve_conflict(cities, comparison_list):
# check if comparison list visits all cities in cities exactly once
cities = [tuple(city) for city in cities]
comparison_list_copy = [tuple(city) for city in comparison_list]
# get cities missing from the comparison list
missed = iter(set(cities) - set(comparison_list_copy))
# visited cities in the comparison list
visited = set()
for i, city in enumerate(comparison_list_copy):
if city not in visited:
visited.update({city})
else:
comparison_list[i] = list(next(missed))
return comparison_list
def crossover(cities, parent1, parent2, start_index, end_index):
child1 = parent1[0:start_index] + parent2[start_index:end_index+1] + parent1[end_index+1:len(parent1)]
child2 = parent2[0:start_index] + parent1[start_index:end_index+1] + parent2[end_index+1:len(parent2)]
child1 = resolve_conflict(cities, child1)# resolve conflict and duplication
child2 = resolve_conflict(cities, child2)
return child1, child2
def mutate(path, mutate_prob):
# take a path and mutate it with a mutation probability
for i in range(len(path)):
fate = np.random.choice([0,1], p=[1-mutate_prob, mutate_prob])
if fate == 1:
new_loc = random.randint(0, len(path)-1)
city1 = path[i]
city2 = path[new_loc]
path[i] = city2
path[new_loc] = city1
return path
if __name__ == '__main__':
# parse data
num_cities, cities = read_data('input.txt')
size = 1000
parent_size = math.ceil(size/2)
start_index = math.floor(size/2)
end_index = math.floor(size)
MUTATE_PROB = 0.03
TOP_K = 10
population = create_initial_pop(size, cities)
max_gen = max(50, (1/num_cities) * 7500)
rank_list = rank_fitness(population)
max_fitness = rank_list[0][1]
best_path = population[rank_list[0][0]]
gen = 0
while gen < max_gen:
new_population = []
parents_list = select_parents(parent_size, population, rank_list, TOP_K)
for parents in parents_list:
new_population.append(parents[0])
new_population.append(parents[1])
child1, child2 = crossover(cities, parents[0], parents[1], start_index, end_index)
child1 = mutate(child1, MUTATE_PROB)
child2 = mutate(child2, MUTATE_PROB)
new_population.append(child1)
new_population.append(child2)
population = new_population
rank_list = rank_fitness(population)
if rank_list[0][1] > max_fitness:
max_fitness = rank_list[0][1]
best_path = population[rank_list[0][0]]
gen += 1
with open('output.txt', 'w') as f:
for city in best_path + [best_path[0]]:
f.write(" ".join([str(coord) for coord in city]))
f.write("\n")