Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GitHub actions #286

Merged
merged 6 commits into from
Apr 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading