Skip to content

Commit 1950260

Browse files
committed
Draw both points of two_points_crossover at random
The second point was always the first plus num_genes // 2, so every child took exactly half of its genes from its second parent. Draw 2 different points in [0, num_genes] instead, every pair equally likely. This also fixes the TypeError with a single gene. Fixes #370
1 parent 703461d commit 1950260

2 files changed

Lines changed: 43 additions & 9 deletions

File tree

‎pygad/utils/crossover.py‎

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -114,17 +114,22 @@ def two_points_crossover(self, parents, offspring_size):
114114
else:
115115
offspring = numpy.empty(offspring_size, dtype=object)
116116

117-
# Randomly generate all the first K points at which crossover takes place between each two parents.
118-
# This saves time by calling the numpy.random.randint() function only once.
119-
if (parents.shape[1] == 1): # If the chromosome has only a single gene. In this case, this gene is copied from the second parent.
120-
crossover_points_1 = numpy.zeros(offspring_size[0])
121-
else:
122-
crossover_points_1 = numpy.random.randint(low=0,
123-
high=numpy.ceil(parents.shape[1]/2 + 1),
124-
size=offspring_size[0])
117+
# Randomly generate all the K pairs of points at which crossover takes place between each two parents.
118+
# This saves time by calling the numpy.random.randint() function only twice.
119+
# The 2 points of a pair are different values in [0, num_genes], and every such pair is equally likely.
120+
# If the chromosome has only a single gene, the points are 0 and 1: the gene is copied from the second parent.
121+
points_a = numpy.random.randint(low=0,
122+
high=parents.shape[1] + 1,
123+
size=offspring_size[0])
124+
points_b = numpy.random.randint(low=0,
125+
high=parents.shape[1],
126+
size=offspring_size[0])
127+
# Skip the value of the first point so that the 2 points differ.
128+
points_b[points_b >= points_a] += 1
125129

126130
# The second point must always be greater than the first point.
127-
crossover_points_2 = crossover_points_1 + int(parents.shape[1]/2)
131+
crossover_points_1 = numpy.minimum(points_a, points_b)
132+
crossover_points_2 = numpy.maximum(points_a, points_b)
128133

129134
for k in range(offspring_size[0]):
130135

‎tests/test_crossover_mutation.py‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,32 @@ def test_random_mutation_manual_call4():
241241
for value in comp_sorted:
242242
assert value in value_space
243243

244+
def test_two_points_crossover_manual_call():
245+
# Both points are random: the genes between them form 1 segment of any length from 1 to num_genes.
246+
num_genes = 10
247+
num_offspring = 1000
248+
result, ga_instance = output_crossover_mutation(gene_type=int,
249+
crossover_type="two_points")
250+
251+
parents = numpy.array([[0] * num_genes,
252+
[1] * num_genes])
253+
offspring = ga_instance.two_points_crossover(parents=parents,
254+
offspring_size=(num_offspring, num_genes))
255+
256+
# Without crossover_probability, the first parent of offspring k is parents[k % 2].
257+
# Mark the genes that come from the second parent with 1.
258+
from_second_parent = numpy.array([child if k % 2 == 0 else 1 - child for k, child in enumerate(offspring)])
259+
260+
segment_lengths = set()
261+
for child in from_second_parent:
262+
segment = numpy.flatnonzero(child)
263+
# The genes from the second parent are consecutive.
264+
assert len(segment) > 0
265+
assert segment[-1] - segment[0] + 1 == len(segment)
266+
segment_lengths.add(len(segment))
267+
268+
assert segment_lengths == set(range(1, num_genes + 1))
269+
244270
if __name__ == "__main__":
245271
#### Single-objective
246272
print()
@@ -285,3 +311,6 @@ def test_random_mutation_manual_call4():
285311

286312
test_random_mutation_manual_call4()
287313
print()
314+
315+
test_two_points_crossover_manual_call()
316+
print()

0 commit comments

Comments
 (0)