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