Skip to content

Commit 091e747

Browse files
authored
PyGAD 2.12.0
## PyGAD 2.12.0 Release Date: 20 February 2021 1. 4 new instance attributes are added to hold temporary results after each generation: `last_generation_fitness` holds the fitness values of the solutions in the last generation, `last_generation_parents` holds the parents selected from the last generation, `last_generation_offspring_crossover` holds the offspring generated after applying the crossover in the last generation, and `last_generation_offspring_mutation` holds the offspring generated after applying the mutation in the last generation. You can access these attributes inside the `on_generation()` method for example. 2. A bug fixed when the `initial_population` parameter is used. The bug occurred due to a mismatch between the data type of the array assigned to `initial_population` and the gene type in the `gene_type` attribute. Assuming that the array assigned to the `initial_population` parameter is `((1, 1), (3, 3), (5, 5), (7, 7))` which has type `int`. When `gene_type` is set to `float`, then the genes will not be float but casted to `int` because the defined array has `int` type. The bug is fixed by forcing the array assigned to `initial_population` to have the data type in the `gene_type` attribute. Check the [issue at GitHub](#27): #27 Thanks to [Marios Giouvanakis](https://www.researchgate.net/profile/Marios-Giouvanakis), a PhD candidate in Electrical & Computer Engineer, [Aristotle University of Thessaloniki (Αριστοτέλειο Πανεπιστήμιο Θεσσαλονίκης), Greece](https://www.auth.gr/en), for emailing me about these issues.
1 parent b21d768 commit 091e747

File tree

3 files changed

+33
-27
lines changed

3 files changed

+33
-27
lines changed

__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11

2-
__version__ = "2.11.0"
2+
__version__ = "2.12.0"

example.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@ def fitness_func(solution, solution_idx):
4343
def callback_generation(ga_instance):
4444
global last_fitness
4545
print("Generation = {generation}".format(generation=ga_instance.generations_completed))
46-
print("Fitness = {fitness}".format(fitness=ga_instance.best_solution()[1]))
47-
print("Change = {change}".format(change=ga_instance.best_solution()[1] - last_fitness))
48-
last_fitness = ga_instance.best_solution()[1]
46+
print("Fitness = {fitness}".format(fitness=ga_instance.best_solution(pop_fitness=ga_instance.last_generation_fitness)[1]))
47+
print("Change = {change}".format(change=ga_instance.best_solution(pop_fitness=ga_instance.last_generation_fitness)[1] - last_fitness))
48+
last_fitness = ga_instance.best_solution(pop_fitness=ga_instance.last_generation_fitness)[1]
4949

5050
# Creating an instance of the GA class inside the ga module. Some parameters are initialized within the constructor.
5151
ga_instance = pygad.GA(num_generations=num_generations,

pygad.py

+29-23
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,8 @@ def __init__(self,
185185
elif numpy.array(initial_population).ndim != 2:
186186
raise ValueError("A 2D list is expected to the initail_population parameter but a ({initial_population_ndim}-D) list found.".format(initial_population_ndim=numpy.array(initial_population).ndim))
187187
else:
188-
self.initial_population = numpy.array(initial_population)
188+
# Forcing the initial_population array to have the data type assigned to the gene_type parameter.
189+
self.initial_population = numpy.array(initial_population, dtype=self.gene_type)
189190
self.population = self.initial_population.copy() # A NumPy array holding the initial population.
190191
self.num_genes = self.initial_population.shape[1] # Number of genes in the solution.
191192
self.sol_per_pop = self.initial_population.shape[0] # Number of solutions in the population.
@@ -647,6 +648,11 @@ def __init__(self,
647648
self.save_best_solutions = save_best_solutions
648649
self.best_solutions = [] # Holds the best solution in each generation.
649650

651+
self.last_generation_fitness = None # A list holding the fitness values of all solutions in the last generation.
652+
self.last_generation_parents = None # A list holding the parents of the last generation.
653+
self.last_generation_offspring_crossover = None # A list holding the offspring after applying crossover in the last generation.
654+
self.last_generation_offspring_mutation = None # A list holding the offspring after applying mutation in the last generation.
655+
650656
def initialize_population(self, low, high):
651657

652658
"""
@@ -748,12 +754,12 @@ def run(self):
748754
self.on_start(self)
749755

750756
for generation in range(self.num_generations):
751-
# Measuring the fitness of each chromosome in the population.
752-
fitness = self.cal_pop_fitness()
757+
# Measuring the fitness of each chromosome in the population. Save the fitness in the last_generation_fitness attribute.
758+
self.last_generation_fitness = self.cal_pop_fitness()
753759
if not (self.on_fitness is None):
754-
self.on_fitness(self, fitness)
760+
self.on_fitness(self, self.last_generation_fitness)
755761

756-
best_solution, best_solution_fitness, best_match_idx = self.best_solution(pop_fitness=fitness)
762+
best_solution, best_solution_fitness, best_match_idx = self.best_solution(pop_fitness=self.last_generation_fitness)
757763

758764
# Appending the fitness value of the best solution in the current generation to the best_solutions_fitness attribute.
759765
self.best_solutions_fitness.append(best_solution_fitness)
@@ -763,42 +769,42 @@ def run(self):
763769
self.best_solutions.append(best_solution)
764770

765771
# Selecting the best parents in the population for mating.
766-
parents = self.select_parents(fitness, num_parents=self.num_parents_mating)
772+
self.last_generation_parents = self.select_parents(self.last_generation_fitness, num_parents=self.num_parents_mating)
767773
if not (self.on_parents is None):
768-
self.on_parents(self, parents)
774+
self.on_parents(self, self.last_generation_parents)
769775

770776
# If self.crossover_type=None, then no crossover is applied and thus no offspring will be created in the next generations. The next generation will use the solutions in the current population.
771777
if self.crossover_type is None:
772778
if self.num_offspring <= self.keep_parents:
773-
offspring_crossover = parents[0:self.num_offspring]
779+
self.last_generation_offspring_crossover = self.last_generation_parents[0:self.num_offspring]
774780
else:
775-
offspring_crossover = numpy.concatenate((parents, self.population[0:(self.num_offspring - parents.shape[0])]))
781+
self.last_generation_offspring_crossover = numpy.concatenate((self.last_generation_parents, self.population[0:(self.num_offspring - self.last_generation_parents.shape[0])]))
776782
else:
777783
# Generating offspring using crossover.
778-
offspring_crossover = self.crossover(parents,
784+
self.last_generation_offspring_crossover = self.crossover(self.last_generation_parents,
779785
offspring_size=(self.num_offspring, self.num_genes))
780786
if not (self.on_crossover is None):
781-
self.on_crossover(self, offspring_crossover)
787+
self.on_crossover(self, self.last_generation_offspring_crossover)
782788

783789
# If self.mutation_type=None, then no mutation is applied and thus no changes are applied to the offspring created using the crossover operation. The offspring will be used unchanged in the next generation.
784790
if self.mutation_type is None:
785-
offspring_mutation = offspring_crossover
791+
self.last_generation_offspring_mutation = self.last_generation_offspring_crossover
786792
else:
787793
# Adding some variations to the offspring using mutation.
788-
offspring_mutation = self.mutation(offspring_crossover)
794+
self.last_generation_offspring_mutation = self.mutation(self.last_generation_offspring_crossover)
789795
if not (self.on_mutation is None):
790-
self.on_mutation(self, offspring_mutation)
796+
self.on_mutation(self, self.last_generation_offspring_mutation)
791797

792798
if (self.keep_parents == 0):
793-
self.population = offspring_mutation
799+
self.population = self.last_generation_offspring_mutation
794800
elif (self.keep_parents == -1):
795801
# Creating the new population based on the parents and offspring.
796-
self.population[0:parents.shape[0], :] = parents
797-
self.population[parents.shape[0]:, :] = offspring_mutation
802+
self.population[0:self.last_generation_parents.shape[0], :] = self.last_generation_parents
803+
self.population[self.last_generation_parents.shape[0]:, :] = self.last_generation_offspring_mutation
798804
elif (self.keep_parents > 0):
799-
parents_to_keep = self.steady_state_selection(fitness, num_parents=self.keep_parents)
805+
parents_to_keep = self.steady_state_selection(self.last_generation_fitness, num_parents=self.keep_parents)
800806
self.population[0:parents_to_keep.shape[0], :] = parents_to_keep
801-
self.population[parents_to_keep.shape[0]:, :] = offspring_mutation
807+
self.population[parents_to_keep.shape[0]:, :] = self.last_generation_offspring_mutation
802808

803809
self.generations_completed = generation + 1 # The generations_completed attribute holds the number of the last completed generation.
804810

@@ -813,17 +819,17 @@ def run(self):
813819

814820
time.sleep(self.delay_after_gen)
815821

816-
last_gen_fitness = self.cal_pop_fitness()
822+
self.last_generation_fitness = self.cal_pop_fitness()
817823
# Save the fitness value of the best solution.
818-
_, best_solution_fitness, _ = self.best_solution(pop_fitness=last_gen_fitness)
824+
_, best_solution_fitness, _ = self.best_solution(pop_fitness=self.last_generation_fitness)
819825
self.best_solutions_fitness.append(best_solution_fitness)
820826

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

825831
if not (self.on_stop is None):
826-
self.on_stop(self, last_gen_fitness)
832+
self.on_stop(self, self.last_generation_fitness)
827833

828834
# Converting the 'best_solutions' list into a NumPy array.
829835
self.best_solutions = numpy.array(self.best_solutions)
@@ -1410,7 +1416,7 @@ def adaptive_mutation_population_fitness(self, offspring):
14101416
It returns the average fitness to be used in adaptive mutation.
14111417
"""
14121418

1413-
fitness = self.cal_pop_fitness()
1419+
fitness = self.last_generation_fitness.copy()
14141420
temp_population = numpy.zeros_like(self.population)
14151421
if self.keep_parents == 0:
14161422
temp_population = offspring

0 commit comments

Comments
 (0)