diff --git a/cgp/__init__.py b/cgp/__init__.py index 11a2341e..4e82a974 100644 --- a/cgp/__init__.py +++ b/cgp/__init__.py @@ -6,6 +6,8 @@ __url__ = "https://happy-algorithms-league.github.io/hal-cgp/" __doc__ = f"{__description__} <{__url__}>" +import warnings + from . import ea, local_search, utils from .cartesian_graph import CartesianGraph, atomic_operator from .genome import Genome @@ -14,3 +16,5 @@ from .node import OperatorNode from .node_impl import Add, ConstantFloat, Div, IfElse, Mul, Parameter, Pow, Sub from .population import Population + +warnings.simplefilter("always", DeprecationWarning) diff --git a/cgp/hl_api.py b/cgp/hl_api.py index 6da6ad5c..4d5b5b7f 100644 --- a/cgp/hl_api.py +++ b/cgp/hl_api.py @@ -1,3 +1,4 @@ +import warnings from typing import Callable, Optional import numpy as np @@ -11,7 +12,8 @@ def evolve( pop: Population, objective: Callable[[IndividualBase], IndividualBase], ea: MuPlusLambda, - min_fitness: float, + min_fitness: float = np.inf, + termination_fitness: float = np.inf, max_generations: int = np.inf, max_objective_calls: int = np.inf, print_progress: Optional[bool] = False, @@ -33,6 +35,10 @@ def evolve( `initialize_fitness_parents` and `step` method. min_fitness : float Minimum fitness at which the evolution is stopped. + Warning: This argument is deprecated and will be removed in the 0.4 + release. Please use `termination_fitness` instead. + termination_fitness : float + Minimum fitness at which the evolution is terminated max_generations : int Maximum number of generations. Defaults to positive infinity. @@ -50,6 +56,23 @@ def evolve( ------- None """ + if np.isfinite(min_fitness) and np.isfinite(termination_fitness): + raise RuntimeError( + "Both `min_fitness` and `termination_fitness` have been set. The " + "`min_fitness` argument is deprecated and will be removed in the 0.4 " + "release. Please use `termination_fitness` instead." + ) + + if np.isfinite(min_fitness): + warnings.warn( + DeprecationWarning( + "The `min_fitness` argument is deprecated and " + "will be removed in the 0.4 release. Please use " + "`termination_fitness` instead." + ) + ) + termination_fitness = min_fitness + if np.isinf(max_generations) and np.isinf(max_objective_calls): raise ValueError("Either max_generations or max_objective_calls must be finite.") @@ -91,7 +114,7 @@ def evolve( if callback is not None: callback(pop) - if pop.champion.fitness + 1e-10 >= min_fitness: + if pop.champion.fitness + 1e-10 >= termination_fitness: break if print_progress: diff --git a/examples/example_caching.py b/examples/example_caching.py index 67eff2ce..4123bdd9 100644 --- a/examples/example_caching.py +++ b/examples/example_caching.py @@ -84,7 +84,7 @@ def objective(individual): "levels_back": 2, "primitives": (cgp.Add, cgp.Sub, cgp.Mul, cgp.ConstantFloat), }, - "evolve_params": {"max_generations": 200, "min_fitness": -1e-12}, + "evolve_params": {"max_generations": 200, "termination_fitness": -1e-12}, } # %% diff --git a/examples/example_differential_evo_regression.py b/examples/example_differential_evo_regression.py index e5593a74..18f1a958 100644 --- a/examples/example_differential_evo_regression.py +++ b/examples/example_differential_evo_regression.py @@ -113,7 +113,7 @@ def objective(individual, seed): "k_local_search": 2, } -evolve_params = {"max_generations": int(args["--max-generations"]), "min_fitness": 0.0} +evolve_params = {"max_generations": int(args["--max-generations"]), "termination_fitness": 0.0} # use an uneven number of gradient steps so they can not easily # average out for clipped values diff --git a/examples/example_evo_regression.py b/examples/example_evo_regression.py index fc4ca58b..57744749 100644 --- a/examples/example_evo_regression.py +++ b/examples/example_evo_regression.py @@ -136,7 +136,7 @@ def evolution(f_target): ea_params = {"n_offsprings": 10, "tournament_size": 2, "mutation_rate": 0.03, "n_processes": 2} - evolve_params = {"max_generations": int(args["--max-generations"]), "min_fitness": 0.0} + evolve_params = {"max_generations": int(args["--max-generations"]), "termination_fitness": 0.0} # create population that will be evolved pop = cgp.Population(**population_params, genome_params=genome_params) diff --git a/examples/example_fec_caching.py b/examples/example_fec_caching.py index 28076892..03f6e168 100644 --- a/examples/example_fec_caching.py +++ b/examples/example_fec_caching.py @@ -99,7 +99,7 @@ def objective(individual): "levels_back": 2, "primitives": (cgp.Add, cgp.Sub, cgp.Mul, cgp.ConstantFloat), }, - "evolve_params": {"max_generations": 200, "min_fitness": -1e-12}, + "evolve_params": {"max_generations": 200, "termination_fitness": -1e-12}, } # %% diff --git a/examples/example_hurdles.py b/examples/example_hurdles.py index 92636c12..1db7b52b 100644 --- a/examples/example_hurdles.py +++ b/examples/example_hurdles.py @@ -132,7 +132,7 @@ def objective_two(individual): "hurdle_percentile": [0.5, 0.0], } -evolve_params = {"max_generations": int(args["--max-generations"]), "min_fitness": 0.0} +evolve_params = {"max_generations": int(args["--max-generations"]), "termination_fitness": 0.0} # %% # We create a population that will be evolved diff --git a/examples/example_local_search_evolution_strategies.py b/examples/example_local_search_evolution_strategies.py index 2f2181d5..85702669 100644 --- a/examples/example_local_search_evolution_strategies.py +++ b/examples/example_local_search_evolution_strategies.py @@ -101,7 +101,7 @@ def objective(individual, seed): "k_local_search": 2, } -evolve_params = {"max_generations": int(args["--max-generations"]), "min_fitness": 0.0} +evolve_params = {"max_generations": int(args["--max-generations"]), "termination_fitness": 0.0} # restrict the number of steps in the local search; since parameter # values are propagated from parents to offsprings, parameter values diff --git a/examples/example_minimal.py b/examples/example_minimal.py index 13529e89..70640e50 100644 --- a/examples/example_minimal.py +++ b/examples/example_minimal.py @@ -83,7 +83,7 @@ def objective(individual): ea_params = {"n_offsprings": 4, "mutation_rate": 0.03, "n_processes": 2} -evolve_params = {"max_generations": int(args["--max-generations"]), "min_fitness": 0.0} +evolve_params = {"max_generations": int(args["--max-generations"]), "termination_fitness": 0.0} # %% # We create a population that will be evolved diff --git a/examples/example_mountain_car.py b/examples/example_mountain_car.py index 1a5b4d47..c9caab51 100644 --- a/examples/example_mountain_car.py +++ b/examples/example_mountain_car.py @@ -168,7 +168,10 @@ def evolve(seed): ea_params = {"n_offsprings": 4, "tournament_size": 1, "mutation_rate": 0.04, "n_processes": 4} - evolve_params = {"max_generations": int(args["--max-generations"]), "min_fitness": 100.0} + evolve_params = { + "max_generations": int(args["--max-generations"]), + "termination_fitness": 100.0, + } pop = cgp.Population(**population_params, genome_params=genome_params) diff --git a/examples/example_multi_genome.py b/examples/example_multi_genome.py index 4717bff4..ce077ae1 100644 --- a/examples/example_multi_genome.py +++ b/examples/example_multi_genome.py @@ -84,7 +84,7 @@ def objective(individual): ea_params = {"n_offsprings": 4, "mutation_rate": 0.03, "n_processes": 1} -evolve_params = {"max_generations": int(args["--max-generations"]), "min_fitness": 0.0} +evolve_params = {"max_generations": int(args["--max-generations"]), "termination_fitness": 0.0} # %% # We create a population that will be evolved diff --git a/examples/example_parametrized_nodes.py b/examples/example_parametrized_nodes.py index 5ca2b274..e5135f9c 100644 --- a/examples/example_parametrized_nodes.py +++ b/examples/example_parametrized_nodes.py @@ -113,7 +113,7 @@ def objective(individual, seed): ea_params = {"n_offsprings": 4, "tournament_size": 1, "mutation_rate": 0.04, "n_processes": 2} -evolve_params = {"max_generations": int(args["--max-generations"]), "min_fitness": 0.0} +evolve_params = {"max_generations": int(args["--max-generations"]), "termination_fitness": 0.0} local_search_params = {"lr": 1e-3, "gradient_steps": 9} diff --git a/examples/example_piecewise_target_function.py b/examples/example_piecewise_target_function.py index 7bc5a6ac..2aee6360 100644 --- a/examples/example_piecewise_target_function.py +++ b/examples/example_piecewise_target_function.py @@ -90,7 +90,7 @@ def objective(individual, rng): ea_params = {"n_offsprings": 4, "mutation_rate": 0.03, "n_processes": 2} -evolve_params = {"max_generations": int(args["--max-generations"]), "min_fitness": 0.0} +evolve_params = {"max_generations": int(args["--max-generations"]), "termination_fitness": 0.0} # create population that will be evolved pop = cgp.Population(**population_params, genome_params=genome_params) diff --git a/examples/example_reorder.py b/examples/example_reorder.py index f662b433..47da116b 100644 --- a/examples/example_reorder.py +++ b/examples/example_reorder.py @@ -93,7 +93,7 @@ def objective(individual): "reorder_genome": True, } -evolve_params = {"max_generations": int(args["--max-generations"]), "min_fitness": 0.0} +evolve_params = {"max_generations": int(args["--max-generations"]), "termination_fitness": 0.0} # %% # We create two populations that will be evolved diff --git a/test/test_hl_api.py b/test/test_hl_api.py index cbb3ba4d..fd87a176 100644 --- a/test/test_hl_api.py +++ b/test/test_hl_api.py @@ -203,3 +203,19 @@ def test_speedup_parallel_evolve(population_params, genome_params, ea_params): else: # assert that multiprocessing roughly follows a linear speedup. assert T == pytest.approx(T_baseline / n_processes, rel=0.25) + + +def test_min_fitness_deprecation(population_params, genome_params, ea_params): + def objective(individual): + individual.fitness = 1.0 + return individual + + pop = cgp.Population(**population_params, genome_params=genome_params) + ea = cgp.ea.MuPlusLambda(**ea_params) + with pytest.warns(DeprecationWarning): + cgp.evolve(pop, objective, ea, min_fitness=2.0, max_generations=10) + + with pytest.raises(RuntimeError): + cgp.evolve( + pop, objective, ea, min_fitness=2.0, termination_fitness=1.5, max_generations=10 + )