Skip to content

Commit

Permalink
Merge pull request #286 from ahmedfgad/github-actions
Browse files Browse the repository at this point in the history
GitHub actions
  • Loading branch information
ahmedfgad authored Apr 14, 2024
2 parents 272aaa5 + 8bbc4c8 commit 0e8be9d
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 35 deletions.
6 changes: 4 additions & 2 deletions docs/source/pygad.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
2 changes: 1 addition & 1 deletion examples/example_multi_objective.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
5 changes: 3 additions & 2 deletions pygad/pygad.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion pygad/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
from pygad.utils import mutation
from pygad.utils import nsga2

__version__ = "1.1.1"
__version__ = "1.2.1"
83 changes: 54 additions & 29 deletions pygad/utils/crossover.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,27 @@ 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
elif len(indices) == 1:
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:
Expand All @@ -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:
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand All @@ -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:
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand All @@ -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]

Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand All @@ -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:
Expand Down

0 comments on commit 0e8be9d

Please sign in to comment.