From 1250a58187d7f2445fd2a0710e3029b726e05a08 Mon Sep 17 00:00:00 2001 From: Henrik Mettler Date: Fri, 30 Oct 2020 11:39:30 +0100 Subject: [PATCH] Added unit test for counting the number of objective calls. And test to ensure finite evolve --- cgp/ea/mu_plus_lambda.py | 8 +++---- test/test_ea_mu_plus_lambda.py | 40 ++++++++++++++++++++++++++++++++++ test/test_hl_api.py | 18 +++++++++++++++ 3 files changed, 62 insertions(+), 4 deletions(-) diff --git a/cgp/ea/mu_plus_lambda.py b/cgp/ea/mu_plus_lambda.py index c4de9c66..09844e16 100644 --- a/cgp/ea/mu_plus_lambda.py +++ b/cgp/ea/mu_plus_lambda.py @@ -52,7 +52,7 @@ def __init__( self.local_search = local_search self.k_local_search = k_local_search - self.n_objective_calls = 0 + self.n_objective_calls: int = 0 def initialize_fitness_parents( self, pop: Population, objective: Callable[[IndividualBase], IndividualBase] @@ -104,8 +104,6 @@ def step( # population instead of the other way around combined = offsprings + pop.parents - self.update_n_objective_calls(combined) - # we follow a two-step process for selection of new parents: # we first determine the fitness for all individuals, then, if # applicable, we apply local search to the k_local_search @@ -150,6 +148,8 @@ def _compute_fitness( self, combined: List[IndividualBase], objective: Callable[[IndividualBase], IndividualBase] ) -> List[IndividualBase]: + self.update_n_objective_calls(combined) + # computes fitness on all individuals, objective functions # should return immediately if fitness is not None if self.n_processes == 1: @@ -193,5 +193,5 @@ def update_n_objective_calls(self, combined: List[IndividualBase]) -> None: i.e., for which the objective function will be evaluated. """ for individual in combined: - if individual.fitness is not None: + if individual.fitness is None: self.n_objective_calls += 1 diff --git a/test/test_ea_mu_plus_lambda.py b/test/test_ea_mu_plus_lambda.py index 4753fef7..57348a9f 100644 --- a/test/test_ea_mu_plus_lambda.py +++ b/test/test_ea_mu_plus_lambda.py @@ -221,3 +221,43 @@ def test_create_new_parent_population(population_params, genome_params, ea_param # we picked the first three individuals new_parents = ea._create_new_parent_population(3, pop.parents) assert new_parents == pop.parents[:3] + + +def test_update_n_objective_calls(population_params, genome_params, ea_params): + def objective(individual): + individual.fitness = float(individual.idx) + return individual + + n_objective_calls_expected = 0 + pop = cgp.Population(**population_params, genome_params=genome_params) + ea = cgp.ea.MuPlusLambda(**ea_params) + assert ea.n_objective_calls == n_objective_calls_expected + + ea.initialize_fitness_parents(pop, objective) + n_objective_calls_expected = population_params["n_parents"] + assert ea.n_objective_calls == n_objective_calls_expected + + n_generations = 100 + for _ in range(n_generations): + offsprings = ea._create_new_offspring_generation(pop) + combined = offsprings + pop.parents + n_objective_calls_expected += sum([1 for ind in combined if ind.fitness is None]) + combined = ea._compute_fitness(combined, objective) + assert n_objective_calls_expected == ea.n_objective_calls + + +def test_update_n_objective_calls_mutation_rate_one(population_params, genome_params, ea_params): + def objective(individual): + individual.fitness = float(individual.idx) + return individual + + population_params["mutation_rate"] = 1.0 + pop = cgp.Population(**population_params, genome_params=genome_params) + ea = cgp.ea.MuPlusLambda(**ea_params) + ea.initialize_fitness_parents(pop, objective) + n_objective_calls_expected = population_params["n_parents"] + n_step_calls = 100 + for idx_current_step in range(n_step_calls): + ea.step(pop, objective) + n_objective_calls_expected += ea_params["n_offsprings"] + assert ea.n_objective_calls == n_objective_calls_expected diff --git a/test/test_hl_api.py b/test/test_hl_api.py index 62f70a0e..84a55ff9 100644 --- a/test/test_hl_api.py +++ b/test/test_hl_api.py @@ -135,6 +135,24 @@ def f1(x): assert abs(pop.champion.fitness) == pytest.approx(0.0) +def test_finite_max_generations_or_max_objective_calls( + population_params, genome_params, ea_params +): + def objective(individual): + individual.fitness = float(individual.idx) + return individual + + pop = cgp.Population(**population_params, genome_params=genome_params) + ea = cgp.ea.MuPlusLambda(**ea_params) + evolve_params = { + "max_generations": np.inf, + "min_fitness": 0, + "max_objective_calls": np.inf, + } + with pytest.raises(ValueError): + cgp.evolve(pop, objective, ea, **evolve_params) + + def _objective_speedup_parallel_evolve(individual): time.sleep(0.25)