Skip to content

Commit 5d4fd93

Browse files
0.35.2
1 parent 5d7d910 commit 5d4fd93

9 files changed

Lines changed: 395 additions & 234 deletions

File tree

notebooks/spot_rosenbrock_6d.ipynb

Lines changed: 188 additions & 224 deletions
Large diffs are not rendered by default.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
77

88
[project]
99
name = "spotpython"
10-
version = "0.35.1"
10+
version = "0.35.2"
1111
authors = [
1212
{ name="T. Bartz-Beielstein", email="tbb@bartzundbartz.de" }
1313
]

src/spotpython/spot/spot.py

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
from spotpython.utils.convert import get_shape
3737
from spotpython.utils.init import fun_control_init, optimizer_control_init, surrogate_control_init, design_control_init
3838
from spotpython.utils.compare import selectNew
39-
from spotpython.utils.aggregate import aggregate_mean_var, select_distant_points
39+
from spotpython.utils.aggregate import aggregate_mean_var, select_distant_points, select_best_cluster
4040
from spotpython.utils.repair import remove_nan, repair_non_numeric
4141
from spotpython.utils.file import get_experiment_filename, get_result_filename
4242
from spotpython.budget.ocba import get_ocba_X
@@ -215,10 +215,6 @@ def __init__(
215215
# Kernel selection from fun_control (NEW)
216216
self.kernel = None
217217
self.kernel_params = None
218-
if fun_control is not None:
219-
self.kernel = fun_control.get("kernel", "gauss")
220-
self.kernel_params = fun_control.get("kernel_params", {})
221-
222218
self.counter = 0
223219
self.success_rate = 0.0
224220
self.success_counter = 0
@@ -227,6 +223,7 @@ def __init__(
227223
# small value:
228224
self.eps = sqrt(spacing(1))
229225

226+
self.selection_method = "distant" # or "best"
230227
self._set_fun(fun)
231228

232229
self._set_bounds_and_dim()
@@ -449,11 +446,16 @@ def _set_additional_attributes(self) -> None:
449446
self.progress_file = self.fun_control["progress_file"]
450447
self.tkagg = self.fun_control["tkagg"]
451448
self.min_success_rate = self.fun_control["min_success_rate"]
449+
self.selection_method = self.fun_control["selection_method"]
452450
# self.success_counter = 0
453451
if self.tkagg:
454452
matplotlib.use("TkAgg")
455453
self.verbosity = self.fun_control["verbosity"]
456454
self.acquisition_failure_strategy = self.fun_control["acquisition_failure_strategy"]
455+
self.kernel = self.fun_control["kernel"]
456+
self.kernel_params = self.fun_control["kernel_params"]
457+
458+
# Surrogate control attributes:
457459
self.max_surrogate_points = self.surrogate_control["max_surrogate_points"]
458460
self.use_nystrom = self.surrogate_control["use_nystrom"]
459461
self.nystrom_m = self.surrogate_control["nystrom_m"]
@@ -1640,6 +1642,57 @@ def update_stats(self) -> None:
16401642
# variance of the best mean y value so far:
16411643
self.min_var_y = self.var_y[argmin(self.mean_y)]
16421644

1645+
def selection_dispatcher(self) -> Tuple[np.ndarray, np.ndarray]:
1646+
"""
1647+
Dispatcher for selection methods.
1648+
Depending on the value of `self.selection_method`,
1649+
it calls the appropriate selection function.
1650+
Args:
1651+
self (object): Spot object
1652+
Returns:
1653+
Tuple[numpy.ndarray, numpy.ndarray]:
1654+
selected design points and their corresponding function values
1655+
Attributes:
1656+
self.selection_method (str):
1657+
selection method to use
1658+
self.X (numpy.ndarray):
1659+
design points
1660+
self.y (numpy.ndarray):
1661+
function values
1662+
self.max_surrogate_points (int):
1663+
maximum number of points to select
1664+
Examples:
1665+
>>> import numpy as np
1666+
from spotpython.fun.objectivefunctions import Analytical
1667+
from spotpython.spot import spot
1668+
from spotpython.utils.init import (
1669+
fun_control_init, optimizer_control_init, surrogate_control_init, design_control_init
1670+
)
1671+
# number of initial points:
1672+
ni = 0
1673+
X_start = np.array([[0, 0], [0, 1], [ 1, 0], [1, 1], [1, 1]])
1674+
fun = Analytical().fun_sphere
1675+
fun_control = fun_control_init(
1676+
lower = np.array([-1, -1]),
1677+
upper = np.array([1, 1])
1678+
)
1679+
design_control=design_control_init(init_size=ni)
1680+
S = spot.Spot(fun=fun,
1681+
fun_control=fun_control,
1682+
design_control=design_control,)
1683+
S.initialize_design(X_start=X_start)
1684+
S.update_stats()
1685+
X_S, y_S = S.selection_dispatcher()
1686+
print(f"Selected X_S: {X_S}")
1687+
print(f"Selected y_S: {y_S}")
1688+
"""
1689+
if self.selection_method == "distant":
1690+
return select_distant_points(X=self.X, y=self.y, k=self.max_surrogate_points)
1691+
elif self.selection_method == "best":
1692+
return select_best_cluster(X=self.X, y=self.y, k=self.max_surrogate_points)
1693+
# If no selection is needed, return all points:
1694+
return self.X, self.y
1695+
16431696
def fit_surrogate(self) -> None:
16441697
"""
16451698
Fit surrogate model. The surrogate model
@@ -1705,15 +1758,15 @@ def fit_surrogate(self) -> None:
17051758
logger.debug("In fit_surrogate(): self.X.shape: %s", self.X.shape)
17061759
logger.debug("In fit_surrogate(): self.y.shape: %s", self.y.shape)
17071760
# Pass kernel options to surrogate if Kriging is used
1708-
if hasattr(self.surrogate, "kernel"):
1761+
if hasattr(self.surrogate, "kernel") and isinstance(self.surrogate, Kriging):
17091762
self.surrogate.kernel = self.kernel
17101763
self.surrogate.kernel_params = self.kernel_params
17111764
X_points = self.X.shape[0]
17121765
y_points = self.y.shape[0]
17131766
if X_points == y_points:
17141767
if (X_points > self.max_surrogate_points) and (self.use_nystrom is False):
1715-
logger.info("Selecting distant points for surrogate fitting.")
1716-
X_S, y_S = select_distant_points(X=self.X, y=self.y, k=self.max_surrogate_points)
1768+
logger.info("Selecting subset of points for surrogate fitting.")
1769+
X_S, y_S = self.selection_dispatcher()
17171770
else:
17181771
X_S = self.X
17191772
y_S = self.y

src/spotpython/utils/aggregate.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,3 +296,41 @@ def select_distant_points(X, y, k):
296296
# Select the corresponding y values
297297
selected_y = y[indices]
298298
return selected_points, selected_y
299+
300+
301+
def select_best_cluster(X, y, k):
302+
"""
303+
Selects all points from the cluster whose center has the smallest mean y value.
304+
305+
Args:
306+
X (numpy.ndarray): X array, shape `(n, k)`.
307+
y (numpy.ndarray): values, shape `(n,)`.
308+
k (int): number of clusters.
309+
310+
Returns:
311+
(numpy.ndarray): selected `X` values from the best cluster, shape `(m, k)`.
312+
(numpy.ndarray): selected `y` values from the best cluster, shape `(m,)`.
313+
314+
Examples:
315+
>>> X = np.array([[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]])
316+
>>> y = np.array([5, 4, 3, 2, 1])
317+
>>> X_best, y_best = select_best_cluster(X, y, 2)
318+
>>> print(X_best)
319+
>>> print(y_best)
320+
"""
321+
# Perform k-means clustering
322+
kmeans = KMeans(n_clusters=k, random_state=0, n_init="auto").fit(X)
323+
labels = kmeans.labels_
324+
# Compute mean y for each cluster
325+
cluster_means = []
326+
for cluster_idx in range(k):
327+
cluster_y = y[labels == cluster_idx]
328+
if len(cluster_y) == 0:
329+
cluster_means.append(np.inf)
330+
else:
331+
cluster_means.append(np.mean(cluster_y))
332+
# Find cluster with smallest mean y
333+
best_cluster = np.argmin(cluster_means)
334+
# Select all points from the best cluster
335+
mask = labels == best_cluster
336+
return X[mask], y[mask]

src/spotpython/utils/init.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ def fun_control_init(
8888
scaler_name=None,
8989
scenario=None,
9090
seed=123,
91+
selection_method="distant",
9192
show_config=False,
9293
show_models=False,
9394
show_progress=True,
@@ -291,6 +292,10 @@ def fun_control_init(
291292
The scenario to use. Default is None. Can be "river", "sklearn", or "lightning".
292293
seed (int):
293294
The seed to use for the random number generator. Default is 123.
295+
selection_method (str):
296+
The method to select points for surrogate fitting when the number of points
297+
exceeds max_surrogate_points. Can be either "distant" or "best".
298+
Default is "distant".
294299
sigma (float):
295300
The standard deviation of the noise of the objective function.
296301
show_progress (bool):
@@ -423,6 +428,7 @@ def fun_control_init(
423428
'prep_model_name': None,
424429
'scenario': "lightning",
425430
'seed': 1234,
431+
'selection_method': "distant",
426432
'show_batch_interval': 1000000,
427433
'shuffle': None,
428434
'sigma': 0.0,
@@ -530,6 +536,7 @@ def fun_control_init(
530536
"scaler_name": scaler_name,
531537
"scenario": scenario,
532538
"seed": seed,
539+
"selection_method": selection_method,
533540
"show_batch_interval": 1_000_000,
534541
"show_config": show_config,
535542
"show_models": show_models,

test/test_get_spot_attributes_as_df.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ def test_get_spot_attributes_as_df():
7373
'progress_file',
7474
'red_dim',
7575
'rng',
76+
'selection_method',
7677
'show_models',
7778
'show_progress',
7879
'spot_writer',

test/test_spot_2.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import numpy as np
2+
from math import inf
3+
from spotpython.fun.objectivefunctions import Analytical
4+
from spotpython.spot import Spot
5+
from spotpython.surrogate.kriging import Kriging
6+
fun = Analytical().fun_branin
7+
from spotpython.utils.init import fun_control_init, design_control_init
8+
# Needed for the sklearn surrogates:
9+
from sklearn.gaussian_process import GaussianProcessRegressor
10+
from sklearn.gaussian_process.kernels import RBF
11+
from sklearn.tree import DecisionTreeRegressor
12+
from sklearn.ensemble import RandomForestRegressor
13+
from sklearn import linear_model
14+
15+
16+
PREFIX = "04"
17+
fun_control = fun_control_init(
18+
PREFIX=PREFIX,
19+
lower = np.array([-5,-0]),
20+
upper = np.array([10,15]),
21+
fun_evals=10,
22+
max_time=1)
23+
24+
design_control = design_control_init(
25+
init_size=10)
26+
27+
S_0 = Kriging(name='kriging', seed=123)
28+
29+
30+
def test_spot_2_GP():
31+
kernel = 1 * RBF(length_scale=1.0, length_scale_bounds=(1e-2, 1e2))
32+
S_GP = GaussianProcessRegressor(kernel=kernel, n_restarts_optimizer=9)
33+
isinstance(S_GP, GaussianProcessRegressor)
34+
isinstance(S_0, Kriging)
35+
fun = Analytical(seed=123).fun_branin
36+
spot_2_GP = Spot(fun=fun,
37+
fun_control=fun_control,
38+
design_control=design_control,
39+
surrogate = S_GP)
40+
spot_2_GP.run()
41+
success_GP = True
42+
assert success_GP is True

test/test_spot_run_1.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import numpy as np
2+
from spotpython.fun.objectivefunctions import Analytical
3+
from spotpython.spot import Spot
4+
from spotpython.utils.init import fun_control_init, design_control_init, surrogate_control_init
5+
6+
dim = 2
7+
lower = np.full(dim, -2)
8+
upper = np.full(dim, 2)
9+
fun = Analytical().fun_rosenbrock
10+
fun_evals = 10
11+
12+
init_size = 5
13+
use_nystrom = False
14+
method = "regression"
15+
infill_criterion = "y"
16+
tolerance_x = 1e-9
17+
seed = 321
18+
max_surrogate_points = 7
19+
min_Lambda = -4
20+
max_Lambda = 3
21+
min_theta = -3
22+
max_theta = 2
23+
isotropic = False
24+
kernel = "matern"
25+
kernel_params = {"nu": 1.5}
26+
selection_method = "distance"
27+
min_success_rate = 0.2
28+
29+
fun_control = fun_control_init(
30+
lower=lower,
31+
upper=upper,
32+
fun_evals=fun_evals,
33+
seed=seed,
34+
show_progress=True,
35+
infill_criterion=infill_criterion,
36+
tolerance_x=tolerance_x,
37+
TENSORBOARD_CLEAN=True,
38+
tensorboard_log=True,
39+
kernel=kernel,
40+
kernel_params=kernel_params,
41+
selection_method=selection_method,
42+
min_success_rate=min_success_rate
43+
)
44+
design_control = design_control_init(init_size=init_size)
45+
surrogate_control_exact = surrogate_control_init(use_nystrom=use_nystrom, method=method, max_surrogate_points=max_surrogate_points, min_Lambda=min_Lambda, max_Lambda=max_Lambda, min_theta=min_theta, max_theta=max_theta, isotropic=isotropic)
46+
47+
def test_run_1():
48+
spot_exact_y = Spot(
49+
fun=fun,
50+
fun_control=fun_control,
51+
design_control=design_control,
52+
surrogate_control=surrogate_control_exact
53+
)
54+
spot_exact_y.run()
55+
exact_success_y = True
56+
assert exact_success_y is True

test/test_update_success_rate.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
@pytest.fixture
66
def spot_instance():
77
# Minimal Spot instance with required attributes
8-
fun_control = {"lower": np.array([-1, -1]), "upper": np.array([1, 1]), "var_type": ["num", "num"], "fun_evals": 10, "fun_repeats": 1, "max_time": 1e6, "noise": False, "tolerance_x": 0, "ocba_delta": 0, "log_level": 50, "show_models": False, "show_progress": False, "infill_criterion": "ei", "n_points": 1, "seed": 1, "progress_file": None, "tkagg": False, "verbosity": 0, "acquisition_failure_strategy": "random", "fun_mo2so": None, "penalty_NA": 0, "PREFIX": "test", "tensorboard_log": False, "spot_tensorboard_path": None, "save_experiment": False, "db_dict_name": None, "save_result": False, "var_name": ["x0", "x1"], "min_success_rate": 0.0}
8+
fun_control = {"lower": np.array([-1, -1]), "upper": np.array([1, 1]), "var_type": ["num", "num"], "fun_evals": 10, "fun_repeats": 1, "max_time": 1e6, "noise": False, "tolerance_x": 0, "ocba_delta": 0, "log_level": 50, "show_models": False, "show_progress": False, "infill_criterion": "ei", "n_points": 1, "seed": 1, "progress_file": None, "tkagg": False, "verbosity": 0, "acquisition_failure_strategy": "random", "fun_mo2so": None, "penalty_NA": 0, "PREFIX": "test", "tensorboard_log": False, "spot_tensorboard_path": None, "save_experiment": False, "db_dict_name": None, "save_result": False, "var_name": ["x0", "x1"], "min_success_rate": 0.0, "selection_method": "distant", "kernel": "gauss", "kernel_params": {}}
99
design_control = {"init_size": 5, "repeats": 1}
1010
from spotpython.utils.init import surrogate_control_init, optimizer_control_init
1111
surrogate_control = surrogate_control_init()

0 commit comments

Comments
 (0)