Skip to content

Commit 8a2a5ae

Browse files
SimonBlankefkiraly
andauthored
Add optimization algorithms from GFO (hyperactive-project#127)
This PR adds all optimization algorithms from GFO in the new `hyperactive v5` API, to the `opt` module. --------- Co-authored-by: Franz Király <fkiraly@gcos.ai>
1 parent 5880221 commit 8a2a5ae

32 files changed

Lines changed: 2961 additions & 67 deletions

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ test-search_space:
3636
done
3737

3838
test-pytest:
39-
python -m pytest --durations=10 -x -p no:warnings tests/; \
39+
python -m pytest --durations=10 -x -p no:warnings tests/ src/hyperactive/; \
4040

4141
test-timings:
4242
cd tests/_local_test_timings; \

scripts/__init__.py

Whitespace-only changes.

scripts/_generator.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import os
2+
from pathlib import Path
3+
4+
# List of algorithm names and corresponding class names
5+
algo_info = [
6+
("downhill_simplex", "DownhillSimplexOptimizer"),
7+
("simulated_annealing", "SimulatedAnnealingOptimizer"),
8+
("direct_algorithm", "DirectAlgorithm"),
9+
("lipschitz_optimization", "LipschitzOptimizer"),
10+
("pattern_search", "PatternSearch"),
11+
("random_restart_hill_climbing", "RandomRestartHillClimbingOptimizer"),
12+
("random_search", "RandomSearchOptimizer"),
13+
("powells_method", "PowellsMethod"),
14+
("differential_evolution", "DifferentialEvolutionOptimizer"),
15+
("evolution_strategy", "EvolutionStrategyOptimizer"),
16+
("genetic_algorithm", "GeneticAlgorithmOptimizer"),
17+
("parallel_tempering", "ParallelTemperingOptimizer"),
18+
("particle_swarm_optimization", "ParticleSwarmOptimizer"),
19+
("spiral_optimization", "SpiralOptimization"),
20+
("bayesian_optimization", "BayesianOptimizer"),
21+
("forest_optimizer", "ForestOptimizer"),
22+
("tree_structured_parzen_estimators", "TreeStructuredParzenEstimators"),
23+
]
24+
25+
BASE_DIR = Path("generated_opt_algos")
26+
27+
28+
# Template for the Python class file
29+
def create_class_file_content(class_name: str) -> str:
30+
return f'''from hyperactive.opt._adapters._gfo import _BaseGFOadapter
31+
32+
33+
class {class_name}(_BaseGFOadapter):
34+
35+
def _get_gfo_class(self):
36+
"""Get the GFO class to use.
37+
38+
Returns
39+
-------
40+
class
41+
The GFO class to use. One of the concrete GFO classes
42+
"""
43+
from gradient_free_optimizers import {class_name}
44+
45+
return {class_name}
46+
'''
47+
48+
49+
# Main generation loop
50+
for name, class_name in algo_info:
51+
algo_folder = BASE_DIR / name
52+
algo_folder.mkdir(parents=True, exist_ok=True)
53+
54+
init_file = algo_folder / "__init__.py"
55+
class_file = algo_folder / f"_{name}.py"
56+
57+
# Create __init__.py (empty)
58+
init_file.touch(exist_ok=True)
59+
60+
# Write the optimizer class file
61+
class_file.write_text(create_class_file_content(class_name))
62+
63+
print(f"Generated {len(algo_info)} folders in {BASE_DIR.resolve()}")

src/hyperactive/base/_optimizer.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Base class for optimizer."""
2+
23
# copyright: hyperactive developers, MIT License (see LICENSE file)
34

45
from skbase.base import BaseObject

src/hyperactive/opt/__init__.py

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,54 @@
11
"""Individual optimization algorithms."""
2+
23
# copyright: hyperactive developers, MIT License (see LICENSE file)
34

45
from hyperactive.opt.gridsearch import GridSearchSk
5-
from hyperactive.opt.hillclimbing import HillClimbing
6-
from hyperactive.opt.hillclimbing_repulsing import HillClimbingRepulsing
7-
from hyperactive.opt.hillclimbing_stochastic import HillClimbingStochastic
6+
from .gfo import (
7+
HillClimbing,
8+
StochasticHillClimbing,
9+
RepulsingHillClimbing,
10+
SimulatedAnnealing,
11+
DownhillSimplexOptimizer,
12+
RandomSearch,
13+
GridSearch,
14+
RandomRestartHillClimbing,
15+
PowellsMethod,
16+
PatternSearch,
17+
LipschitzOptimizer,
18+
DirectAlgorithm,
19+
ParallelTempering,
20+
ParticleSwarmOptimizer,
21+
SpiralOptimization,
22+
GeneticAlgorithm,
23+
EvolutionStrategy,
24+
DifferentialEvolution,
25+
BayesianOptimizer,
26+
TreeStructuredParzenEstimators,
27+
ForestOptimizer,
28+
)
29+
830

931
__all__ = [
1032
"GridSearchSk",
1133
"HillClimbing",
12-
"HillClimbingRepulsing",
13-
"HillClimbingStochastic",
34+
"RepulsingHillClimbing",
35+
"StochasticHillClimbing",
36+
"SimulatedAnnealing",
37+
"DownhillSimplexOptimizer",
38+
"RandomSearch",
39+
"GridSearch",
40+
"RandomRestartHillClimbing",
41+
"PowellsMethod",
42+
"PatternSearch",
43+
"LipschitzOptimizer",
44+
"DirectAlgorithm",
45+
"ParallelTempering",
46+
"ParticleSwarmOptimizer",
47+
"SpiralOptimization",
48+
"GeneticAlgorithm",
49+
"EvolutionStrategy",
50+
"DifferentialEvolution",
51+
"BayesianOptimizer",
52+
"TreeStructuredParzenEstimators",
53+
"ForestOptimizer",
1454
]

src/hyperactive/opt/_adapters/_gfo.py

Lines changed: 69 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Adapter for gfo package."""
2+
23
# copyright: hyperactive developers, MIT License (see LICENSE file)
34

45
from hyperactive.base import BaseOptimizer
@@ -40,9 +41,7 @@ def _get_gfo_class(self):
4041
class
4142
The GFO class to use. One of the concrete GFO classes
4243
"""
43-
raise NotImplementedError(
44-
"This method should be implemented in a subclass."
45-
)
44+
raise NotImplementedError("This method should be implemented in a subclass.")
4645

4746
def get_search_config(self):
4847
"""Get the search configuration.
@@ -55,8 +54,63 @@ def get_search_config(self):
5554
search_config = super().get_search_config()
5655
search_config["initialize"] = self._initialize
5756
del search_config["verbose"]
57+
58+
search_config = self._handle_gfo_defaults(search_config)
59+
60+
search_config["search_space"] = self._to_dict_np(search_config["search_space"])
61+
62+
return search_config
63+
64+
def _handle_gfo_defaults(self, search_config):
65+
"""Handle default values for GFO search configuration.
66+
67+
Temporary measure until GFO handles defaults gracefully.
68+
69+
Parameters
70+
----------
71+
search_config : dict with str keys
72+
The search configuration dictionary to handle defaults for.
73+
74+
Returns
75+
-------
76+
search_config : dict with str keys
77+
The search configuration dictionary with defaults handled.
78+
"""
79+
if "sampling" in search_config and search_config["sampling"] is None:
80+
search_config["sampling"] = {"random": 1000000}
81+
82+
if "tree_para" in search_config and search_config["tree_para"] is None:
83+
search_config["tree_para"] = {"n_estimators": 100}
84+
5885
return search_config
5986

87+
def _to_dict_np(self, search_space):
88+
"""Coerce the search space to a format suitable for gfo optimizers.
89+
90+
gfo expects dicts of numpy arrays, not lists.
91+
This method coerces lists or tuples in the search space to numpy arrays.
92+
93+
Parameters
94+
----------
95+
search_space : dict with str keys and iterable values
96+
The search space to coerce.
97+
98+
Returns
99+
-------
100+
dict with str keys and 1D numpy arrays as values
101+
The coerced search space.
102+
"""
103+
import numpy as np
104+
105+
def coerce_to_numpy(arr):
106+
"""Coerce a list or tuple to a numpy array."""
107+
if not isinstance(arr, np.ndarray):
108+
return np.array(arr)
109+
return arr
110+
111+
coerced_search_space = {k: coerce_to_numpy(v) for k, v in search_space.items()}
112+
return coerced_search_space
113+
60114
def _run(self, experiment, **search_config):
61115
"""Run the optimization search process.
62116
Parameters
@@ -75,15 +129,15 @@ def _run(self, experiment, **search_config):
75129
max_time = search_config.pop("max_time", None)
76130

77131
gfo_cls = self._get_gfo_class()
78-
hcopt = gfo_cls(**search_config)
132+
gfopt = gfo_cls(**search_config)
79133

80134
with StdoutMute(active=not self.verbose):
81-
hcopt.search(
135+
gfopt.search(
82136
objective_function=experiment.score,
83137
n_iter=n_iter,
84138
max_time=max_time,
85139
)
86-
best_params = hcopt.best_para
140+
best_params = gfopt.best_para
87141
return best_params
88142

89143
@classmethod
@@ -143,5 +197,12 @@ def get_test_params(cls, parameter_set="default"):
143197
},
144198
"n_iter": 100,
145199
}
146-
147-
return [params_sklearn, params_ackley]
200+
params_ackley_list = {
201+
"experiment": ackley_exp,
202+
"search_space": {
203+
"x0": list(np.linspace(-5, 5, 10)),
204+
"x1": list(np.linspace(-5, 5, 10)),
205+
},
206+
"n_iter": 100,
207+
}
208+
return [params_sklearn, params_ackley, params_ackley_list]
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
"""Individual optimization algorithms."""
2+
3+
# copyright: hyperactive developers, MIT License (see LICENSE file)
4+
5+
from ._hillclimbing import HillClimbing
6+
from ._stochastic_hillclimbing import StochasticHillClimbing
7+
from ._repulsing_hillclimbing import RepulsingHillClimbing
8+
from ._simulated_annealing import SimulatedAnnealing
9+
from ._downhill_simplex import DownhillSimplexOptimizer
10+
from ._random_search import RandomSearch
11+
from ._grid_search import GridSearch
12+
from ._random_restart_hill_climbing import RandomRestartHillClimbing
13+
from ._powells_method import PowellsMethod
14+
from ._pattern_search import PatternSearch
15+
from ._lipschitz_optimization import LipschitzOptimizer
16+
from ._direct_algorithm import DirectAlgorithm
17+
from ._parallel_tempering import ParallelTempering
18+
from ._particle_swarm_optimization import ParticleSwarmOptimizer
19+
from ._spiral_optimization import SpiralOptimization
20+
from ._genetic_algorithm import GeneticAlgorithm
21+
from ._evolution_strategy import EvolutionStrategy
22+
from ._differential_evolution import DifferentialEvolution
23+
from ._bayesian_optimization import BayesianOptimizer
24+
from ._tree_structured_parzen_estimators import TreeStructuredParzenEstimators
25+
from ._forest_optimizer import ForestOptimizer
26+
27+
28+
__all__ = [
29+
"HillClimbing",
30+
"RepulsingHillClimbing",
31+
"StochasticHillClimbing",
32+
"SimulatedAnnealing",
33+
"DownhillSimplexOptimizer",
34+
"RandomSearch",
35+
"GridSearch",
36+
"RandomRestartHillClimbing",
37+
"PowellsMethod",
38+
"PatternSearch",
39+
"LipschitzOptimizer",
40+
"DirectAlgorithm",
41+
"ParallelTempering",
42+
"ParticleSwarmOptimizer",
43+
"SpiralOptimization",
44+
"GeneticAlgorithm",
45+
"EvolutionStrategy",
46+
"DifferentialEvolution",
47+
"BayesianOptimizer",
48+
"TreeStructuredParzenEstimators",
49+
"ForestOptimizer",
50+
]

0 commit comments

Comments
 (0)