diff --git a/docs/source/pygad.rst b/docs/source/pygad.rst index 0271ce8..9c4179e 100644 --- a/docs/source/pygad.rst +++ b/docs/source/pygad.rst @@ -927,8 +927,10 @@ shuffles their order randomly. ``adaptive_mutation()`` ~~~~~~~~~~~~~~~~~~~~~~~ -Applies the adaptive mutation which selects a subset of genes and -shuffles their order randomly. +Applies the adaptive mutation which selects the number/percentage of +genes to mutate based on the solution's fitness. If the fitness is high +(i.e. solution quality is high), then small number/percentage of genes +is mutated compared to a solution with a low fitness. .. _bestsolution: diff --git a/examples/example_multi_objective.py b/examples/example_multi_objective.py index b6053a2..b0ff1a2 100644 --- a/examples/example_multi_objective.py +++ b/examples/example_multi_objective.py @@ -53,7 +53,7 @@ def on_generation(ga_instance): # Running the GA to optimize the parameters of the function. ga_instance.run() -ga_instance.plot_fitness(labels=['Obj 1', 'Obj 2']) +ga_instance.plot_fitness(label=['Obj 1', 'Obj 2']) # Returning the details of the best solution. solution, solution_fitness, solution_idx = ga_instance.best_solution(ga_instance.last_generation_fitness) diff --git a/pygad/pygad.py b/pygad/pygad.py index f10eb70..dad0be8 100644 --- a/pygad/pygad.py +++ b/pygad/pygad.py @@ -1135,8 +1135,9 @@ def __init__(self, # Validate delay_after_gen if type(delay_after_gen) in GA.supported_int_float_types: - if not self.suppress_warnings: - warnings.warn("The 'delay_after_gen' parameter is deprecated starting from PyGAD 3.3.0. To delay or pause the evolution after each generation, assign a callback function/method to the 'on_generation' parameter to adds some time delay.") + if delay_after_gen != 0.0: + if not self.suppress_warnings: + warnings.warn("The 'delay_after_gen' parameter is deprecated starting from PyGAD 3.3.0. To delay or pause the evolution after each generation, assign a callback function/method to the 'on_generation' parameter to adds some time delay.") if delay_after_gen >= 0.0: self.delay_after_gen = delay_after_gen else: diff --git a/pygad/utils/__init__.py b/pygad/utils/__init__.py index 093cf85..b39eda1 100644 --- a/pygad/utils/__init__.py +++ b/pygad/utils/__init__.py @@ -3,4 +3,4 @@ from pygad.utils import mutation from pygad.utils import nsga2 -__version__ = "1.1.1" \ No newline at end of file +__version__ = "1.2.1" \ No newline at end of file diff --git a/pygad/utils/crossover.py b/pygad/utils/crossover.py index b03d361..e6c489e 100644 --- a/pygad/utils/crossover.py +++ b/pygad/utils/crossover.py @@ -25,15 +25,19 @@ def single_point_crossover(self, parents, offspring_size): else: offspring = numpy.empty(offspring_size, dtype=object) - for k in range(offspring_size[0]): - # The point at which crossover takes place between two parents. Usually, it is at the center. - crossover_point = numpy.random.randint(low=0, high=parents.shape[1], size=1)[0] + # Randomly generate all the K points at which crossover takes place between each two parents. The point does not have to be always at the center of the solutions. + # This saves time by calling the numpy.random.randint() function only once. + crossover_points = numpy.random.randint(low=0, + high=parents.shape[1], + size=offspring_size[0]) + for k in range(offspring_size[0]): + # Check if the crossover_probability parameter is used. if not (self.crossover_probability is None): probs = numpy.random.random(size=parents.shape[0]) - indices = numpy.where(probs <= self.crossover_probability)[0] + indices = list(set(numpy.where(probs <= self.crossover_probability)[0])) - # If no parent satisfied the probability, no crossover is applied and a parent is selected. + # If no parent satisfied the probability, no crossover is applied and a parent is selected as is. if len(indices) == 0: offspring[k, :] = parents[k % parents.shape[0], :] continue @@ -41,7 +45,7 @@ def single_point_crossover(self, parents, offspring_size): parent1_idx = indices[0] parent2_idx = parent1_idx else: - indices = random.sample(list(set(indices)), 2) + indices = random.sample(indices, 2) parent1_idx = indices[0] parent2_idx = indices[1] else: @@ -51,9 +55,9 @@ def single_point_crossover(self, parents, offspring_size): parent2_idx = (k+1) % parents.shape[0] # The new offspring has its first half of its genes from the first parent. - offspring[k, 0:crossover_point] = parents[parent1_idx, 0:crossover_point] + offspring[k, 0:crossover_points[k]] = parents[parent1_idx, 0:crossover_points[k]] # The new offspring has its second half of its genes from the second parent. - offspring[k, crossover_point:] = parents[parent2_idx, crossover_point:] + offspring[k, crossover_points[k]:] = parents[parent2_idx, crossover_points[k]:] if self.allow_duplicate_genes == False: if self.gene_space is None: @@ -86,17 +90,23 @@ def two_points_crossover(self, parents, offspring_size): else: offspring = numpy.empty(offspring_size, dtype=object) + # Randomly generate all the first K points at which crossover takes place between each two parents. + # This saves time by calling the numpy.random.randint() function only once. + if (parents.shape[1] == 1): # If the chromosome has only a single gene. In this case, this gene is copied from the second parent. + crossover_points_1 = numpy.zeros(offspring_size[0]) + else: + crossover_points_1 = numpy.random.randint(low=0, + high=numpy.ceil(parents.shape[1]/2 + 1), + size=offspring_size[0]) + + # The second point must always be greater than the first point. + crossover_points_2 = crossover_points_1 + int(parents.shape[1]/2) + for k in range(offspring_size[0]): - if (parents.shape[1] == 1): # If the chromosome has only a single gene. In this case, this gene is copied from the second parent. - crossover_point1 = 0 - else: - crossover_point1 = numpy.random.randint(low=0, high=numpy.ceil(parents.shape[1]/2 + 1), size=1)[0] - - crossover_point2 = crossover_point1 + int(parents.shape[1]/2) # The second point must always be greater than the first point. if not (self.crossover_probability is None): probs = numpy.random.random(size=parents.shape[0]) - indices = numpy.where(probs <= self.crossover_probability)[0] + indices = list(set(numpy.where(probs <= self.crossover_probability)[0])) # If no parent satisfied the probability, no crossover is applied and a parent is selected. if len(indices) == 0: @@ -106,7 +116,7 @@ def two_points_crossover(self, parents, offspring_size): parent1_idx = indices[0] parent2_idx = parent1_idx else: - indices = random.sample(list(set(indices)), 2) + indices = random.sample(indices, 2) parent1_idx = indices[0] parent2_idx = indices[1] else: @@ -116,11 +126,11 @@ def two_points_crossover(self, parents, offspring_size): parent2_idx = (k+1) % parents.shape[0] # The genes from the beginning of the chromosome up to the first point are copied from the first parent. - offspring[k, 0:crossover_point1] = parents[parent1_idx, 0:crossover_point1] + offspring[k, 0:crossover_points_1[k]] = parents[parent1_idx, 0:crossover_points_1[k]] # The genes from the second point up to the end of the chromosome are copied from the first parent. - offspring[k, crossover_point2:] = parents[parent1_idx, crossover_point2:] + offspring[k, crossover_points_2[k]:] = parents[parent1_idx, crossover_points_2[k]:] # The genes between the 2 points are copied from the second parent. - offspring[k, crossover_point1:crossover_point2] = parents[parent2_idx, crossover_point1:crossover_point2] + offspring[k, crossover_points_1[k]:crossover_points_2[k]] = parents[parent2_idx, crossover_points_1[k]:crossover_points_2[k]] if self.allow_duplicate_genes == False: if self.gene_space is None: @@ -151,10 +161,18 @@ def uniform_crossover(self, parents, offspring_size): else: offspring = numpy.empty(offspring_size, dtype=object) + # Randomly generate all the genes sources at which crossover takes place between each two parents. + # This saves time by calling the numpy.random.randint() function only once. + # There is a list of 0 and 1 for each offspring. + # [0, 1, 0, 0, 1, 1]: If the value is 0, then take the gene from the first parent. If 1, take it from the second parent. + genes_sources = numpy.random.randint(low=0, + high=2, + size=offspring_size) + for k in range(offspring_size[0]): if not (self.crossover_probability is None): probs = numpy.random.random(size=parents.shape[0]) - indices = numpy.where(probs <= self.crossover_probability)[0] + indices = list(set(numpy.where(probs <= self.crossover_probability)[0])) # If no parent satisfied the probability, no crossover is applied and a parent is selected. if len(indices) == 0: @@ -164,7 +182,7 @@ def uniform_crossover(self, parents, offspring_size): parent1_idx = indices[0] parent2_idx = parent1_idx else: - indices = random.sample(list(set(indices)), 2) + indices = random.sample(indices, 2) parent1_idx = indices[0] parent2_idx = indices[1] else: @@ -173,12 +191,11 @@ def uniform_crossover(self, parents, offspring_size): # Index of the second parent to mate. parent2_idx = (k+1) % parents.shape[0] - genes_source = numpy.random.randint(low=0, high=2, size=offspring_size[1]) for gene_idx in range(offspring_size[1]): - if (genes_source[gene_idx] == 0): + if (genes_sources[k, gene_idx] == 0): # The gene will be copied from the first parent if the current gene index is 0. offspring[k, gene_idx] = parents[parent1_idx, gene_idx] - elif (genes_source[gene_idx] == 1): + elif (genes_sources[k, gene_idx] == 1): # The gene will be copied from the second parent if the current gene index is 1. offspring[k, gene_idx] = parents[parent2_idx, gene_idx] @@ -212,10 +229,18 @@ def scattered_crossover(self, parents, offspring_size): else: offspring = numpy.empty(offspring_size, dtype=object) + # Randomly generate all the genes sources at which crossover takes place between each two parents. + # This saves time by calling the numpy.random.randint() function only once. + # There is a list of 0 and 1 for each offspring. + # [0, 1, 0, 0, 1, 1]: If the value is 0, then take the gene from the first parent. If 1, take it from the second parent. + genes_sources = numpy.random.randint(low=0, + high=2, + size=offspring_size) + for k in range(offspring_size[0]): if not (self.crossover_probability is None): probs = numpy.random.random(size=parents.shape[0]) - indices = numpy.where(probs <= self.crossover_probability)[0] + indices = list(set(numpy.where(probs <= self.crossover_probability)[0])) # If no parent satisfied the probability, no crossover is applied and a parent is selected. if len(indices) == 0: @@ -225,7 +250,7 @@ def scattered_crossover(self, parents, offspring_size): parent1_idx = indices[0] parent2_idx = parent1_idx else: - indices = random.sample(list(set(indices)), 2) + indices = random.sample(indices, 2) parent1_idx = indices[0] parent2_idx = indices[1] else: @@ -234,9 +259,9 @@ def scattered_crossover(self, parents, offspring_size): # Index of the second parent to mate. parent2_idx = (k+1) % parents.shape[0] - # A 0/1 vector where 0 means the gene is taken from the first parent and 1 means the gene is taken from the second parent. - gene_sources = numpy.random.randint(0, 2, size=self.num_genes) - offspring[k, :] = numpy.where(gene_sources == 0, parents[parent1_idx, :], parents[parent2_idx, :]) + offspring[k, :] = numpy.where(genes_sources[k] == 0, + parents[parent1_idx, :], + parents[parent2_idx, :]) if self.allow_duplicate_genes == False: if self.gene_space is None: