Skip to content
Draft
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
2 changes: 1 addition & 1 deletion pygad/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@
from pygad.utils import validation
from pygad.utils import engine

__version__ = "1.5.12"
__version__ = "1.5.13"
7 changes: 5 additions & 2 deletions pygad/utils/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -751,8 +751,7 @@ def run_crossover(self):
# 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.
if self.crossover_type is None:
if self.keep_elitism == 0:
num_parents_to_keep = self.num_parents_mating if self.keep_parents == - 1 else self.keep_parents
if self.num_offspring <= num_parents_to_keep:
if self.num_offspring <= self.last_generation_parents.shape[0]:
self.last_generation_offspring_crossover = self.last_generation_parents[0:self.num_offspring]
else:
self.last_generation_offspring_crossover = numpy.concatenate(
Expand All @@ -764,6 +763,10 @@ def run_crossover(self):
num_parents=self.keep_elitism)
if self.num_offspring <= self.keep_elitism:
self.last_generation_offspring_crossover = self.last_generation_parents[0:self.num_offspring]
if self.last_generation_offspring_crossover.shape[0] < self.num_offspring:
self.last_generation_offspring_crossover = numpy.concatenate(
(self.last_generation_offspring_crossover,
self.population[0:(self.num_offspring - self.last_generation_offspring_crossover.shape[0])]))
else:
self.last_generation_offspring_crossover = numpy.concatenate(
(self.last_generation_elitism, self.population[0:(self.num_offspring - self.last_generation_elitism.shape[0])]))
Expand Down
63 changes: 63 additions & 0 deletions tests/test_no_crossover_offspring.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""Public-lifecycle regression tests for no-crossover offspring dimensions."""
import warnings

import numpy
import pytest

import pygad


def fitness_func(ga_instance, solution, solution_idx):
return float(numpy.sum(solution))


@pytest.mark.parametrize('num_parents,keep_elitism,keep_parents', [
(2, 5, None), # More offspring than parents, fewer than elites.
(6, 0, 3), # Fewer offspring than parents, more than retained parents.
(1, 5, None), # One parent must not silently broadcast into three offspring.
(2, 8, None), # All solutions are retained; there are no offspring.
(6, 0, -1),
(2, 0, 0),
(2, 1, None),
])
@pytest.mark.parametrize('mutation_type', [None, 'random'])
def test_no_crossover_preserves_offspring_and_population_dimensions(
num_parents, keep_elitism, keep_parents, mutation_type):
initial_population = numpy.arange(24).reshape(8, 3)
observed_shapes = []

def on_crossover(ga_instance, offspring):
observed_shapes.append(offspring.shape)
assert offspring.shape == (ga_instance.num_offspring, 3)

params = dict(num_generations=2,
num_parents_mating=num_parents,
initial_population=initial_population,
fitness_func=fitness_func,
gene_type=int,
parent_selection_type='sss',
keep_elitism=keep_elitism,
keep_parents=keep_parents,
crossover_type=None,
mutation_type=mutation_type,
on_crossover=on_crossover,
random_seed=42)
if mutation_type is not None:
params['mutation_probability'] = 0.0
with warnings.catch_warnings():
# Retain the library's real validators. The disabled-operators warning
# is expected for the mutation_type=None cases.
warnings.simplefilter('ignore', UserWarning)
ga_instance = pygad.GA(**params)
ga_instance.run()

assert ga_instance.generations_completed == 2
assert ga_instance.population.shape == initial_population.shape
expected_shape = (ga_instance.num_offspring, 3)
assert observed_shapes == [expected_shape, expected_shape]
assert ga_instance.last_generation_offspring_crossover.shape == expected_shape
assert ga_instance.last_generation_offspring_mutation.shape == expected_shape
assert all(any(numpy.array_equal(row, original) for original in initial_population)
for row in ga_instance.population)
if keep_elitism > 0:
assert numpy.array_equal(ga_instance.population[0], initial_population[-1])
Loading