1111_options = dict (
1212 popsize = ("population size" , 20 ),
1313 maxiter = ("maximum number of generations" , 100 ),
14- constraint_aware = ("constraint-aware optimization (True/False)" , False ),
14+ constraint_aware = ("constraint-aware optimization (True/False)" , True ),
1515 method = ("crossover method to use, choose any from single_point, two_point, uniform, disruptive_uniform" , "uniform" ),
1616 mutation_chance = ("chance to mutate is 1 in mutation_chance" , 10 ),
1717)
@@ -36,7 +36,8 @@ def tune(searchspace: Searchspace, runner, tuning_options):
3636 weighted_population = []
3737 for dna in population :
3838 try :
39- time = cost_func (dna , check_restrictions = False )
39+ # if we are not constraint-aware we should check restrictions upon evaluation
40+ time = cost_func (dna , check_restrictions = not constraint_aware )
4041 except util .StopCriterionReached as e :
4142 if tuning_options .verbose :
4243 print (e )
@@ -84,13 +85,24 @@ class GeneticAlgorithm:
8485 def __init__ (self , pop_size , searchspace , constraint_aware = False , method = "uniform" , mutation_chance = 10 ):
8586 self .pop_size = pop_size
8687 self .searchspace = searchspace
88+ self .tune_params = searchspace .tune_params .copy ()
8789 self .constraint_aware = constraint_aware
8890 self .crossover_method = supported_methods [method ]
8991 self .mutation_chance = mutation_chance
9092
9193 def generate_population (self ):
9294 """ Constraint-aware population creation method """
93- return list (list (p ) for p in self .searchspace .get_random_sample (self .pop_size ))
95+ if self .constraint_aware :
96+ pop = list (list (p ) for p in self .searchspace .get_random_sample (self .pop_size ))
97+ else :
98+ pop = []
99+ dna_size = len (self .tune_params )
100+ for _ in range (self .pop_size ):
101+ dna = []
102+ for key in self .tune_params :
103+ dna .append (random .choice (self .tune_params [key ]))
104+ pop .append (dna )
105+ return pop
94106
95107 def crossover (self , dna1 , dna2 ):
96108 """ Apply selected crossover method, repair dna if constraint-aware """
@@ -135,12 +147,24 @@ def mutate(self, dna, cache=False):
135147 """Mutate DNA with 1/mutation_chance chance."""
136148 # this is actually a neighbors problem with Hamming distance, choose randomly from returned searchspace list
137149 if int (random .random () * self .mutation_chance ) == 0 :
138- if cache :
139- neighbors = self .searchspace .get_neighbors (tuple (dna ), neighbor_method = "Hamming" )
150+ if self .constraint_aware :
151+ if cache :
152+ neighbors = self .searchspace .get_neighbors (tuple (dna ), neighbor_method = "Hamming" )
153+ else :
154+ neighbors = self .searchspace .get_neighbors_no_cache (tuple (dna ), neighbor_method = "Hamming" )
155+ if len (neighbors ) > 0 :
156+ return list (random .choice (neighbors ))
140157 else :
141- neighbors = self .searchspace .get_neighbors_no_cache (tuple (dna ), neighbor_method = "Hamming" )
142- if len (neighbors ) > 0 :
143- return list (random .choice (neighbors ))
158+ # select a tunable parameter at random
159+ mutate_index = random .randint (0 , len (self .tune_params )- 1 )
160+ mutate_key = list (self .tune_params .keys ())[mutate_index ]
161+ # get all possible values for this parameter and remove current value
162+ new_val_options = self .tune_params [mutate_key ].copy ()
163+ new_val_options .remove (dna [mutate_index ])
164+ # pick new value at random
165+ if len (new_val_options ) > 0 :
166+ new_val = random .choice (new_val_options )
167+ dna [mutate_index ] = new_val
144168 return dna
145169
146170
0 commit comments