-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathgenetic.py
More file actions
290 lines (222 loc) · 9 KB
/
Copy pathgenetic.py
File metadata and controls
290 lines (222 loc) · 9 KB
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
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
# Homework 4: Solving the Jump-It Game Problem Using a Genetic Algorithm
#
# Authors: Caleb Sutton & Lyubov Sidlinskaya
# Imports
import sys
import random
import jump_it_DP
import time
# Main function executing the genetic algorithm for each puzzle
# should return the most fit chromosome after a specified number
# of generations
def artificial_selection(population, puzzle, max_generations, min_generations, pop_size):
generation_num = 1
fitness_streak = 0
current_most_fit = population[0]
previous_most_fit = population[0]
while generation_num <= max_generations and (generation_num <= min_generations or (fitness_streak/generation_num) < 0.5):
previous_most_fit = current_most_fit
new_generation = []
for _ in range(pop_size//2): # double population
parent1, parent2 = select_parents(population)
new_generation.append(crossover(parent1, parent2, puzzle))
new_generation.append(crossover(parent2, parent1, puzzle))
population = population + new_generation
while len(population) != pop_size:
reduce_pop(population)
for chromosome in population:
if chromosome['fitness'] < current_most_fit['fitness']:
current_most_fit = chromosome
if current_most_fit['fitness'] == previous_most_fit['fitness']:
fitness_streak += 1
generation_num += 1
#print('Total Generations: ' + str(generation_num - 1))
return current_most_fit
# Function which chooses a chromosome using the roulette wheel
# selection mechanism and removes it from the population
def reduce_pop(population):
total_cost = 0
selection_pool = []
random.seed()
for chrom in population:
total_cost += chrom['fitness']
previous_chance = 0
for chrom in population:
chance = chrom['fitness'] / total_cost
selection_pool.append({'chance': chance, 'chromosome': chrom, 'window': [previous_chance, previous_chance + chance]})
previous_chance += chance
selector = random.uniform(0, 1)
for chrom in selection_pool:
if selector >= chrom['window'][0] and selector < chrom['window'][1]:
population.remove(chrom['chromosome'])
break
return True
# Function whcih chooses two parents from a population
# based on roulette wheel selection mechanism
#
# Note: for bonus we can implement multiple selection methods
def select_parents(population):
total_fitness = 0
selection_pool = []
random.seed()
for chrom in population:
total_fitness += 1 / chrom['fitness'] # find the total fitness of the population
previous_chance = 0
for chrom in population:
chance = (1/chrom['fitness']) / total_fitness # this is the percentage chance each chromosome will be selected
selection_pool.append({'chance': chance, 'chromosome': chrom,'window': [previous_chance, previous_chance + chance]})
previous_chance += chance
selector1 = random.uniform(0, 1) # generate a random number that will choose a parent
selector2 = random.uniform(0, 1)
for chrom in selection_pool:
if selector1 > chrom['window'][0] and selector1 < chrom['window'][1]:
while selector2 >= chrom['window'][0] and selector2 < chrom['window'][1]: # as long as the second selector fall with in the selection of range of the first parent generate a new random selector
# the point of this is so both of the parents arent the same chromosome
selector2 = random.uniform(0, 1)
parent1 = chrom['chromosome']
selection_pool.remove(chrom)
for chrom in selection_pool:
if selector2 >= chrom['window'][0] and selector2 < chrom['window'][1]:
parent2 = chrom['chromosome']
selection_pool.remove(chrom)
return(parent1, parent2)
# Function which creates an offspring using the alleles of
# parent1 and parent2, also determines the fitness of the
# offspring and returns the representative dictionary
# {alleles: [], fitness: (total cost)}
#
# Note: need to be careful about bug which could produce
# a chromosome with consecutive 0s
def crossover(parent1, parent2, puzzle):
cross_point = random.randint(0, len(parent1['alleles']) - 2)
offspring = {'alleles': [], 'fitness': 0}
while (parent1['alleles'][cross_point] == 0 and parent2['alleles'][cross_point + 1] == 0): # generate a new cross point as long as
cross_point = random.randint(0, len(parent1['alleles']) - 2)
for i in range(len(parent1['alleles'])):
if i < cross_point + 1:
offspring['alleles'].append(parent1['alleles'][i])
else:
offspring['alleles'].append(parent2['alleles'][i])
offspring['fitness'] = get_fitness(offspring['alleles'], puzzle)
return offspring
# Function which randomly decides whether or not to alter
# one of the chromosomes alleles. Should only happen rarely
def mutate(chromosome):
random.seed()
match = 1
return_val = False
if random.randint(1, 100) == match: # 1% chance of a mututation
i = random.randint(0, len(chromosome['alleles']) -2)
if chromosome['alleles'][i] == 0: # if the allele is a 0 we can just flip it to a 1 now worries
chromosome['alleles'][i] = 1
return_val = True
else: # if it's a 1 we need to make sure the adjacent alleles are also 1 in order to flip to zero
if i == 0 and i + 1 < len(chromosome['alleles']): # Case: first allele
if chromosome['alleles'][i + 1] == 1: # Only Check the next allele
chromosome['alleles'][i] = 0
return_val = True
elif i == len(chromosome['alleles']) - 1: # Case: last allele cannot be 0
chromosome['alleles'][i] = 1 # should never get here....
return_val = False
elif chromosome['alleles'][i + 1] == 1 and chromosome['alleles'][i - 1] == 1: # Case: Somewhere in middle, check previous and next allele
chromosome['alleles'][i] = 0
return_val = True
return return_val
# function which returns the the fitness of an array of
# of alleles using the ***fitness function***
def get_fitness(alleles, puzzle):
board = puzzle[1:]
fittness_count = 0
for i in range(len(board)):
if alleles[i] == 1:
fittness_count += board[i]
return fittness_count
# function whcih generates a population of chromosomes
# of size pop_size and populates the alleles of each
# chromosome
def create_random_population(puzzle, pop_size):
population = []
pop_item_length = len(puzzle) -2
random.seed()
for length in range(pop_size):
chrom = {'alleles' : [], 'fitness': 0}
for i in range(pop_item_length):
bit = random.randint(0, 1)
if i > 0 and bit == 0:
if chrom['alleles'][i-1] == 0:
chrom['alleles'].append(1)
else:
chrom['alleles'].append(bit)
else:
chrom['alleles'].append(bit)
chrom['alleles'].append(1) # The last place in the bitstring is always visited
chrom['fitness'] = get_fitness(chrom['alleles'], puzzle)
population.append(chrom)
return population
def print_path(puzzle, alleles):
i = 1
print('path showing indices of visited cells: 0', end = "")
path_contents = '0'
for bit in alleles:
if bit == 1:
print(' -> ' + str(i), end = "")
path_contents += ' -> ' + str(puzzle[i])
i += 1
print('\npath showing contents of visited cells: ' + path_contents)
return True
# function which reads the input file and returns an array
# of all the puzzles
def read_data(input_file):
list_table = []
try:
with open(input_file, "r") as data_file:
for line in data_file:
item = line.split() # removes EOL marker
item = list(map(int, item))
list_table.append(item)
return list_table
# Throw error if issue reading file.
except IOError:
print("Error reading file.", input_file)
sys.exit()
# function which calls artificial_selection() and dynamic_programming()
# for each puzzle and prints the results to the console
def main():
num_correct = 0
num_total = 0
max_pop = 128
max_generations = 100
min_generations = 25
initial_pop = 512
# for each puzzle call artificial_selection() and dynamic_programming()
# and print the results
if len(sys.argv) > 1:
initial_time = time.time()
input_file_name = sys.argv[1] # Accepts filename as cmd line argument
input_table = read_data(input_file_name)
for puzzle in input_table:
print('\n\nGame Board: ' + str(puzzle))
print('________________________________________\nDP Solution')
cost = [0] * len(puzzle) #create the cache table
path = cost[:] # create a table for path that is identical to path
min_cost = jump_it_DP.jumpIt(puzzle, cost, path)
print("cost: " + str(min_cost))
jump_it_DP.displayPath(puzzle, path)
print('________________________________________\nGA Solution')
pop_size = 2 ** (len(puzzle) - 2)
if pop_size > max_pop:
pop_size = max_pop
most_fit = artificial_selection(create_random_population(puzzle, initial_pop), puzzle, max_generations, min_generations, pop_size)
print('Cost: ' + str(most_fit['fitness']))
print_path(puzzle, most_fit['alleles'])
print('========================================')
if int(min_cost) == int(most_fit['fitness']):
num_correct += 1
num_total += 1
final_time = time.time()
print('\n\n\nGA accuracy: ' + str((num_correct/num_total) * 100) + '%')
print('Total time elapsed: ' + str(final_time - initial_time))
else:
print ("Please enter the correct cmd line arguments in the format:")
print ("python genetic.py input1.txt")
main()