Skip to content

Commit b17bf61

Browse files
authored
Create genetic_algorithm_example.py
1 parent 3fcc1b2 commit b17bf61

File tree

1 file changed

+179
-0
lines changed

1 file changed

+179
-0
lines changed
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import os.path
2+
from tkinter import *
3+
from tkinter import ttk
4+
5+
import search
6+
7+
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
8+
9+
LARGE_FONT = ('Verdana', 12)
10+
EXTRA_LARGE_FONT = ('Consolas', 36, 'bold')
11+
12+
canvas_width = 800
13+
canvas_height = 600
14+
15+
black = '#000000'
16+
white = '#ffffff'
17+
p_blue = '#042533'
18+
lp_blue = '#0c394c'
19+
20+
# genetic algorithm variables
21+
# feel free to play around with these
22+
target = 'Genetic Algorithm' # the phrase to be generated
23+
max_population = 100 # number of samples in each population
24+
mutation_rate = 0.1 # probability of mutation
25+
f_thres = len(target) # fitness threshold
26+
ngen = 1200 # max number of generations to run the genetic algorithm
27+
28+
generation = 0 # counter to keep track of generation number
29+
30+
u_case = [chr(x) for x in range(65, 91)] # list containing all uppercase characters
31+
l_case = [chr(x) for x in range(97, 123)] # list containing all lowercase characters
32+
punctuations1 = [chr(x) for x in range(33, 48)] # lists containing punctuation symbols
33+
punctuations2 = [chr(x) for x in range(58, 65)]
34+
punctuations3 = [chr(x) for x in range(91, 97)]
35+
numerals = [chr(x) for x in range(48, 58)] # list containing numbers
36+
37+
# extend the gene pool with the required lists and append the space character
38+
gene_pool = []
39+
gene_pool.extend(u_case)
40+
gene_pool.extend(l_case)
41+
gene_pool.append(' ')
42+
43+
44+
# callbacks to update global variables from the slider values
45+
def update_max_population(slider_value):
46+
global max_population
47+
max_population = slider_value
48+
49+
50+
def update_mutation_rate(slider_value):
51+
global mutation_rate
52+
mutation_rate = slider_value
53+
54+
55+
def update_f_thres(slider_value):
56+
global f_thres
57+
f_thres = slider_value
58+
59+
60+
def update_ngen(slider_value):
61+
global ngen
62+
ngen = slider_value
63+
64+
65+
# fitness function
66+
def fitness_fn(_list):
67+
fitness = 0
68+
# create string from list of characters
69+
phrase = ''.join(_list)
70+
# add 1 to fitness value for every matching character
71+
for i in range(len(phrase)):
72+
if target[i] == phrase[i]:
73+
fitness += 1
74+
return fitness
75+
76+
77+
# function to bring a new frame on top
78+
def raise_frame(frame, init=False, update_target=False, target_entry=None, f_thres_slider=None):
79+
frame.tkraise()
80+
global target
81+
if update_target and target_entry is not None:
82+
target = target_entry.get()
83+
f_thres_slider.config(to=len(target))
84+
if init:
85+
population = search.init_population(max_population, gene_pool, len(target))
86+
genetic_algorithm_stepwise(population)
87+
88+
89+
# defining root and child frames
90+
root = Tk()
91+
f1 = Frame(root)
92+
f2 = Frame(root)
93+
94+
# pack frames on top of one another
95+
for frame in (f1, f2):
96+
frame.grid(row=0, column=0, sticky='news')
97+
98+
# Home Screen (f1) widgets
99+
target_entry = Entry(f1, font=('Consolas 46 bold'), exportselection=0, foreground=p_blue, justify=CENTER)
100+
target_entry.insert(0, target)
101+
target_entry.pack(expand=YES, side=TOP, fill=X, padx=50)
102+
target_entry.focus_force()
103+
104+
max_population_slider = Scale(f1, from_=3, to=1000, orient=HORIZONTAL, label='Max population',
105+
command=lambda value: update_max_population(int(value)))
106+
max_population_slider.set(max_population)
107+
max_population_slider.pack(expand=YES, side=TOP, fill=X, padx=40)
108+
109+
mutation_rate_slider = Scale(f1, from_=0, to=1, orient=HORIZONTAL, label='Mutation rate', resolution=0.0001,
110+
command=lambda value: update_mutation_rate(float(value)))
111+
mutation_rate_slider.set(mutation_rate)
112+
mutation_rate_slider.pack(expand=YES, side=TOP, fill=X, padx=40)
113+
114+
f_thres_slider = Scale(f1, from_=0, to=len(target), orient=HORIZONTAL, label='Fitness threshold',
115+
command=lambda value: update_f_thres(int(value)))
116+
f_thres_slider.set(f_thres)
117+
f_thres_slider.pack(expand=YES, side=TOP, fill=X, padx=40)
118+
119+
ngen_slider = Scale(f1, from_=1, to=5000, orient=HORIZONTAL, label='Max number of generations',
120+
command=lambda value: update_ngen(int(value)))
121+
ngen_slider.set(ngen)
122+
ngen_slider.pack(expand=YES, side=TOP, fill=X, padx=40)
123+
124+
button = ttk.Button(f1, text='RUN',
125+
command=lambda: raise_frame(f2, init=True, update_target=True, target_entry=target_entry,
126+
f_thres_slider=f_thres_slider)).pack(side=BOTTOM, pady=50)
127+
128+
# f2 widgets
129+
canvas = Canvas(f2, width=canvas_width, height=canvas_height)
130+
canvas.pack(expand=YES, fill=BOTH, padx=20, pady=15)
131+
button = ttk.Button(f2, text='EXIT', command=lambda: raise_frame(f1)).pack(side=BOTTOM, pady=15)
132+
133+
134+
# function to run the genetic algorithm and update text on the canvas
135+
def genetic_algorithm_stepwise(population):
136+
root.title('Genetic Algorithm')
137+
for generation in range(ngen):
138+
# generating new population after selecting, recombining and mutating the existing population
139+
population = [
140+
search.mutate(search.recombine(*search.select(2, population, fitness_fn)), gene_pool, mutation_rate) for i
141+
in range(len(population))]
142+
# genome with the highest fitness in the current generation
143+
current_best = ''.join(max(population, key=fitness_fn))
144+
# collecting first few examples from the current population
145+
members = [''.join(x) for x in population][:48]
146+
147+
# clear the canvas
148+
canvas.delete('all')
149+
# displays current best on top of the screen
150+
canvas.create_text(canvas_width / 2, 40, fill=p_blue, font='Consolas 46 bold', text=current_best)
151+
152+
# displaying a part of the population on the screen
153+
for i in range(len(members) // 3):
154+
canvas.create_text((canvas_width * .175), (canvas_height * .25 + (25 * i)), fill=lp_blue,
155+
font='Consolas 16', text=members[3 * i])
156+
canvas.create_text((canvas_width * .500), (canvas_height * .25 + (25 * i)), fill=lp_blue,
157+
font='Consolas 16', text=members[3 * i + 1])
158+
canvas.create_text((canvas_width * .825), (canvas_height * .25 + (25 * i)), fill=lp_blue,
159+
font='Consolas 16', text=members[3 * i + 2])
160+
161+
# displays current generation number
162+
canvas.create_text((canvas_width * .5), (canvas_height * 0.95), fill=p_blue, font='Consolas 18 bold',
163+
text=f'Generation {generation}')
164+
165+
# displays blue bar that indicates current maximum fitness compared to maximum possible fitness
166+
scaling_factor = fitness_fn(current_best) / len(target)
167+
canvas.create_rectangle(canvas_width * 0.1, 90, canvas_width * 0.9, 100, outline=p_blue)
168+
canvas.create_rectangle(canvas_width * 0.1, 90, canvas_width * 0.1 + scaling_factor * canvas_width * 0.8, 100,
169+
fill=lp_blue)
170+
canvas.update()
171+
172+
# checks for completion
173+
fittest_individual = search.fitness_threshold(fitness_fn, f_thres, population)
174+
if fittest_individual:
175+
break
176+
177+
178+
raise_frame(f1)
179+
root.mainloop()

0 commit comments

Comments
 (0)