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