Skip to content

Commit c858f27

Browse files
authored
New parameter in best_solution() method
The `best_solution()` method accepts a new optional parameter called `pop_fitness`. It accepts a list of the fitness values of the solutions in the population. If `None`, then the `cal_pop_fitness()` method is called to calculate the fitness values of the population.
1 parent 90bd275 commit c858f27

File tree

1 file changed

+29
-12
lines changed

1 file changed

+29
-12
lines changed

pygad.py

+29-12
Original file line numberDiff line numberDiff line change
@@ -280,22 +280,35 @@ def __init__(self,
280280
# The mutation_num_genes parameter does not exist. Checking whether adaptive mutation is used.
281281
if (mutation_type != "adaptive"):
282282
# The percent of genes to mutate is fixed not adaptive.
283-
if type(mutation_percent_genes) in [int, float, numpy.int, numpy.int8, numpy.int16, numpy.int32, numpy.int64, numpy.float, numpy.float16, numpy.float32, numpy.float64]:
283+
if mutation_percent_genes == 'default'.lower():
284+
mutation_percent_genes = 10
285+
# Based on the mutation percentage in the 'mutation_percent_genes' parameter, the number of genes to mutate is calculated.
286+
mutation_num_genes = numpy.uint32((mutation_percent_genes*self.num_genes)/100)
287+
# Based on the mutation percentage of genes, if the number of selected genes for mutation is less than the least possible value which is 1, then the number will be set to 1.
288+
if mutation_num_genes == 0:
289+
if self.mutation_probability is None:
290+
if not self.suppress_warnings: warnings.warn("The percentage of genes to mutate (mutation_percent_genes={mutation_percent}) resutled in selecting ({mutation_num}) genes. The number of genes to mutate is set to 1 (mutation_num_genes=1).\nIf you do not want to mutate any gene, please set mutation_type=None.".format(mutation_percent=mutation_percent_genes, mutation_num=mutation_num_genes))
291+
mutation_num_genes = 1
292+
293+
elif type(mutation_percent_genes) in [int, float, numpy.int, numpy.int8, numpy.int16, numpy.int32, numpy.int64, numpy.float, numpy.float16, numpy.float32, numpy.float64]:
284294
if (mutation_percent_genes <= 0 or mutation_percent_genes > 100):
285295
self.valid_parameters = False
286296
raise ValueError("The percentage of selected genes for mutation (mutation_percent_genes) must be > 0 and <= 100 but ({mutation_percent_genes}) found.\n".format(mutation_percent_genes=mutation_percent_genes))
287297
else:
298+
# If mutation_percent_genes equals the string "default", then it is replaced by the numeric value 10.
288299
if mutation_percent_genes == 'default'.lower():
289300
mutation_percent_genes = 10
301+
290302
# Based on the mutation percentage in the 'mutation_percent_genes' parameter, the number of genes to mutate is calculated.
291303
mutation_num_genes = numpy.uint32((mutation_percent_genes*self.num_genes)/100)
292304
# Based on the mutation percentage of genes, if the number of selected genes for mutation is less than the least possible value which is 1, then the number will be set to 1.
293305
if mutation_num_genes == 0:
294-
if not self.suppress_warnings: warnings.warn("The percentage of genes to mutate (mutation_percent_genes={mutation_percent}) resutled in selecting ({mutation_num}) genes. The number of genes to mutate is set to 1 (mutation_num_genes=1).\nIf you do not want to mutate any gene, please set mutation_type=None.".format(mutation_percent=mutation_percent_genes, mutation_num=mutation_num_genes))
306+
if self.mutation_probability is None:
307+
if not self.suppress_warnings: warnings.warn("The percentage of genes to mutate (mutation_percent_genes={mutation_percent}) resutled in selecting ({mutation_num}) genes. The number of genes to mutate is set to 1 (mutation_num_genes=1).\nIf you do not want to mutate any gene, please set mutation_type=None.".format(mutation_percent=mutation_percent_genes, mutation_num=mutation_num_genes))
295308
mutation_num_genes = 1
296309
else:
297310
self.valid_parameters = False
298-
raise ValueError("Unexpected type for the 'mutation_percent_genes' parameter. A numeric value is expected but ({mutation_percent_genes_value}) of type {mutation_percent_genes_type} found.".format(mutation_percent_genes_value=mutation_percent_genes, mutation_percent_genes_type=type(mutation_percent_genes)))
311+
raise ValueError("Unexpected value or type of the 'mutation_percent_genes' parameter. It only accepts the string 'default' or a numeric value but ({mutation_percent_genes_value}) of type {mutation_percent_genes_type} found.".format(mutation_percent_genes_value=mutation_percent_genes, mutation_percent_genes_type=type(mutation_percent_genes)))
299312
else:
300313
# The percent of genes to mutate is adaptive not fixed.
301314
if type(mutation_percent_genes) in [list, tuple, numpy.ndarray]:
@@ -323,7 +336,7 @@ def __init__(self,
323336
self.valid_parameters = False
324337
raise ValueError("When mutation_type='adaptive', then the 'mutation_percent_genes' parameter must have only 2 elements but ({mutation_percent_genes_length}) element(s) found.".format(mutation_percent_genes_length=len(mutation_percent_genes)))
325338
else:
326-
if mutation_percent_genes != 'default'.lower():
339+
if self.mutation_probability is None:
327340
self.valid_parameters = False
328341
raise ValueError("Unexpected type for the 'mutation_percent_genes' parameter. When mutation_type='adaptive', then list/tuple/numpy.ndarray is expected but ({mutation_percent_genes_value}) of type {mutation_percent_genes_type} found.".format(mutation_percent_genes_value=mutation_percent_genes, mutation_percent_genes_type=type(mutation_percent_genes)))
329342
# The mutation_num_genes parameter exists. Checking whether adaptive mutation is used.
@@ -691,7 +704,7 @@ def run(self):
691704
if not (self.on_fitness is None):
692705
self.on_fitness(self, fitness)
693706

694-
best_solution, best_solution_fitness, best_match_idx = self.best_solution()
707+
best_solution, best_solution_fitness, best_match_idx = self.best_solution(pop_fitness=fitness)
695708

696709
# Appending the fitness value of the best solution in the current generation to the best_solutions_fitness attribute.
697710
self.best_solutions_fitness.append(best_solution_fitness)
@@ -751,16 +764,17 @@ def run(self):
751764

752765
time.sleep(self.delay_after_gen)
753766

767+
last_gen_fitness = self.cal_pop_fitness()
754768
# Save the fitness value of the best solution.
755-
_, best_solution_fitness, _ = self.best_solution()
769+
_, best_solution_fitness, _ = self.best_solution(pop_fitness=last_gen_fitness)
756770
self.best_solutions_fitness.append(best_solution_fitness)
757771

758772
self.best_solution_generation = numpy.where(numpy.array(self.best_solutions_fitness) == numpy.max(numpy.array(self.best_solutions_fitness)))[0][0]
759773
# After the run() method completes, the run_completed flag is changed from False to True.
760774
self.run_completed = True # Set to True only after the run() method completes gracefully.
761775

762776
if not (self.on_stop is None):
763-
self.on_stop(self, self.cal_pop_fitness())
777+
self.on_stop(self, last_gen_fitness)
764778

765779
# Converting the 'best_solutions' list into a NumPy array.
766780
self.best_solutions = numpy.array(self.best_solutions)
@@ -1543,11 +1557,13 @@ def adaptive_mutation_probs_randomly(self, offspring):
15431557
offspring[offspring_idx, gene_idx] = offspring[offspring_idx, gene_idx] + random_value
15441558
return offspring
15451559

1546-
def best_solution(self):
1560+
def best_solution(self, pop_fitness=None):
15471561

15481562
"""
15491563
Returns information about the best solution found by the genetic algorithm.
1550-
The following is returned:
1564+
Accepts the following parameters:
1565+
pop_fitness: An optional parameter holding the fitness values of the solutions in the current population. If None, then the cal_pop_fitness() method is called to calculate the fitness of the population.
1566+
The following are returned:
15511567
-best_solution: Best solution in the current population.
15521568
-best_solution_fitness: Fitness value of the best solution.
15531569
-best_match_idx: Index of the best solution in the current population.
@@ -1561,12 +1577,13 @@ def best_solution(self):
15611577

15621578
# Getting the best solution after finishing all generations.
15631579
# At first, the fitness is calculated for each solution in the final generation.
1564-
fitness = self.cal_pop_fitness()
1580+
if pop_fitness is None:
1581+
pop_fitness = self.cal_pop_fitness()
15651582
# Then return the index of that solution corresponding to the best fitness.
1566-
best_match_idx = numpy.where(fitness == numpy.max(fitness))[0][0]
1583+
best_match_idx = numpy.where(pop_fitness == numpy.max(pop_fitness))[0][0]
15671584

15681585
best_solution = self.population[best_match_idx, :]
1569-
best_solution_fitness = fitness[best_match_idx]
1586+
best_solution_fitness = pop_fitness[best_match_idx]
15701587

15711588
return best_solution, best_solution_fitness, best_match_idx
15721589

0 commit comments

Comments
 (0)