Skip to content

Commit c99aacc

Browse files
kernels with rosen6d example
1 parent 068c737 commit c99aacc

8 files changed

Lines changed: 446 additions & 778 deletions

File tree

notebooks/00_spotPython_tests.ipynb

Lines changed: 139 additions & 614 deletions
Large diffs are not rendered by default.

notebooks/spot_aquisition_random_rosenbrock_2d.ipynb

Lines changed: 190 additions & 76 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.3"
10+
version = "0.36.0"
1111
authors = [
1212
{ name="T. Bartz-Beielstein", email="tbb@bartzundbartz.de" }
1313
]

src/spotpython/spot/spot.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,12 @@
4949
get_ith_hyperparameter_name_from_fun_control,
5050
)
5151
import plotly.graph_objects as go
52-
from typing import Callable
52+
from typing import Callable, Tuple
5353
from spotpython.utils.numpy2json import NumpyEncoder
5454
from spotpython.utils.file import load_result
5555
from spotpython.surrogate.plot import plotkd
5656

57+
5758
# Setting up the backend to use QtAgg
5859
# matplotlib.use("TkAgg")
5960
# matplotlib.use("Agg")
@@ -452,14 +453,14 @@ def _set_additional_attributes(self) -> None:
452453
matplotlib.use("TkAgg")
453454
self.verbosity = self.fun_control["verbosity"]
454455
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"]
457456

458457
# Surrogate control attributes:
459458
self.max_surrogate_points = self.surrogate_control["max_surrogate_points"]
460459
self.use_nystrom = self.surrogate_control["use_nystrom"]
461460
self.nystrom_m = self.surrogate_control["nystrom_m"]
462461
self.nystrom_seed = self.surrogate_control["nystrom_seed"]
462+
self.kernel = self.surrogate_control["kernel"]
463+
self.kernel_params = self.surrogate_control["kernel_params"]
463464

464465
# Internal attributes:
465466
self.X = None
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import numpy as np
2+
from sklearn.gaussian_process.kernels import Kernel, NormalizedKernelMixin, Hyperparameter
3+
4+
5+
def _correlation(kernel, D, kernel_params=None):
6+
"""
7+
Dispatches to the selected kernel function.
8+
Args:
9+
kernel: Kernel type (str, callable, or sklearn-compatible kernel object)
10+
D (np.ndarray): Distance matrix.
11+
kernel_params (dict): Parameters for the kernel (optional).
12+
Returns:
13+
np.ndarray: Correlation matrix.
14+
"""
15+
kernel_params = kernel_params or {}
16+
# If kernel is a sklearn-compatible kernel object
17+
if isinstance(kernel, Kernel):
18+
# These expect X, Y, not D, so we call on D as before for compatibility
19+
return kernel(D)
20+
elif callable(kernel):
21+
return kernel(D, **kernel_params)
22+
elif kernel == "gauss":
23+
return np.exp(-D)
24+
elif kernel == "matern":
25+
nu = kernel_params.get("nu", 2.5)
26+
if nu == 0.5:
27+
return np.exp(-np.sqrt(D))
28+
elif nu == 1.5:
29+
sqrt3D = np.sqrt(3.0 * D)
30+
return (1.0 + sqrt3D) * np.exp(-sqrt3D)
31+
elif nu == 2.5:
32+
sqrt5D = np.sqrt(5.0 * D)
33+
return (1.0 + sqrt5D + (5.0 / 3.0) * D) * np.exp(-sqrt5D)
34+
else:
35+
return np.exp(-D)
36+
elif kernel == "exp":
37+
return np.exp(-np.sqrt(D))
38+
elif kernel == "cubic":
39+
return 1.0 - D**3
40+
elif kernel == "linear":
41+
return 1.0 - D
42+
elif kernel == "rq":
43+
alpha = kernel_params.get("alpha", 1.0)
44+
return (1.0 + D / (2.0 * alpha)) ** (-alpha)
45+
elif kernel == "poly":
46+
degree = kernel_params.get("degree", 2)
47+
return (1.0 + D) ** degree
48+
else:
49+
raise ValueError(f"Unknown kernel: {kernel}")
50+
51+
52+
# Example: Custom sklearn-compatible RBF kernel using _correlation
53+
class CustomRBF(NormalizedKernelMixin, Kernel):
54+
def __init__(self, length_scale=1.0, length_scale_bounds=(1e-5, 1e5)):
55+
self.length_scale = length_scale
56+
self.length_scale_bounds = length_scale_bounds
57+
58+
@property
59+
def hyperparameter_length_scale(self):
60+
return Hyperparameter("length_scale", "numeric", self.length_scale_bounds)
61+
62+
def __call__(self, D, eval_gradient=False):
63+
# D is assumed to be the squared distance matrix
64+
K = _correlation("gauss", D)
65+
if eval_gradient:
66+
# Gradient not implemented
67+
return K, np.empty((K.shape[0], K.shape[1], 0))
68+
return K
69+
70+
def diag(self, X):
71+
return np.ones(X.shape[0])
72+
73+
def is_stationary(self):
74+
return True
75+
76+
77+
class CustomMatern(NormalizedKernelMixin, Kernel):
78+
def __init__(self, length_scale=1.0, nu=1.5, length_scale_bounds=(1e-5, 1e5)):
79+
self.length_scale = length_scale
80+
self.nu = nu
81+
self.length_scale_bounds = length_scale_bounds
82+
83+
@property
84+
def hyperparameter_length_scale(self):
85+
return Hyperparameter("length_scale", "numeric", self.length_scale_bounds)
86+
87+
def __call__(self, D, eval_gradient=False):
88+
K = _correlation("matern", D, {"nu": self.nu})
89+
if eval_gradient:
90+
# Gradient not implemented
91+
return K, np.empty((K.shape[0], K.shape[1], 0))
92+
return K
93+
94+
def diag(self, X):
95+
return np.ones(X.shape[0])
96+
97+
def is_stationary(self):
98+
return True

src/spotpython/surrogate/kriging.py

Lines changed: 3 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -8,53 +8,7 @@
88
from numpy import linspace, append
99
from scipy.spatial.distance import cdist, pdist, squareform
1010
from spotpython.surrogate.plot import plotkd
11-
12-
# --- Kernel functions ---
13-
14-
15-
def gauss_kernel(D):
16-
"""Gaussian (RBF) kernel: exp(-D)"""
17-
return np.exp(-D)
18-
19-
20-
def matern_kernel(D, nu=2.5):
21-
"""Matern kernel (default nu=2.5, smooth)."""
22-
if nu == 0.5:
23-
return np.exp(-np.sqrt(D))
24-
elif nu == 1.5:
25-
sqrt3D = np.sqrt(3.0 * D)
26-
return (1.0 + sqrt3D) * np.exp(-sqrt3D)
27-
elif nu == 2.5:
28-
sqrt5D = np.sqrt(5.0 * D)
29-
return (1.0 + sqrt5D + (5.0 / 3.0) * D) * np.exp(-sqrt5D)
30-
else:
31-
# Fallback to Gaussian for unsupported nu
32-
return np.exp(-D)
33-
34-
35-
def exponential_kernel(D):
36-
"""Exponential kernel: exp(-sqrt(D))"""
37-
return np.exp(-np.sqrt(D))
38-
39-
40-
def cubic_kernel(D):
41-
"""Cubic kernel: 1 - D^3"""
42-
return 1.0 - D**3
43-
44-
45-
def linear_kernel(D):
46-
"""Linear kernel: 1 - D"""
47-
return 1.0 - D
48-
49-
50-
def rational_quadratic_kernel(D, alpha=1.0):
51-
"""Rational Quadratic kernel: (1 + D/(2*alpha))^(-alpha)"""
52-
return (1.0 + D / (2.0 * alpha)) ** (-alpha)
53-
54-
55-
def poly_kernel(D, degree=2):
56-
"""Polynomial kernel: (1 + D)^degree"""
57-
return (1.0 + D) ** degree
11+
from spotpython.surrogate.kernels import _correlation # Only import _correlation
5812

5913

6014
# --- The New Kriging Class with Nyström Approximation as introduced in v0.34.0 ---
@@ -258,38 +212,17 @@ def __init__(
258212
self.Rinv_r_ = None # R^{-1} r (n,)
259213

260214
# --- Kernel dispatch ---
261-
262215
def _correlation(self, D):
263216
"""
264-
Dispatches to the selected kernel function.
217+
Dispatches to the selected kernel function using the shared _correlation() from kernels.py.
265218
266219
Args:
267220
D (np.ndarray): Distance matrix.
268221
269222
Returns:
270223
np.ndarray: Correlation matrix.
271224
"""
272-
if callable(self.kernel):
273-
return self.kernel(D, **self.kernel_params)
274-
elif self.kernel == "gauss":
275-
return gauss_kernel(D)
276-
elif self.kernel == "matern":
277-
nu = self.kernel_params.get("nu", 2.5)
278-
return matern_kernel(D, nu=nu)
279-
elif self.kernel == "exp":
280-
return exponential_kernel(D)
281-
elif self.kernel == "cubic":
282-
return cubic_kernel(D)
283-
elif self.kernel == "linear":
284-
return linear_kernel(D)
285-
elif self.kernel == "rq":
286-
alpha = self.kernel_params.get("alpha", 1.0)
287-
return rational_quadratic_kernel(D, alpha=alpha)
288-
elif self.kernel == "poly":
289-
degree = self.kernel_params.get("degree", 2)
290-
return poly_kernel(D, degree=degree)
291-
else:
292-
raise ValueError(f"Unknown kernel: {self.kernel}")
225+
return _correlation(self.kernel, D, self.kernel_params)
293226

294227
# -------- Basis correlation construction (identical to kriging.py) --------
295228

src/spotpython/utils/init.py

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,6 @@ def fun_control_init(
5757
horizon=None,
5858
hyperdict=None,
5959
infill_criterion="y",
60-
kernel="gauss",
61-
kernel_params={},
6260
log_every_n_steps=50,
6361
log_level=50,
6462
lower=None,
@@ -211,11 +209,6 @@ def fun_control_init(
211209
For example: `spotriver.hyperdict.river_hyper_dict import RiverHyperDict`
212210
infill_criterion (str):
213211
Can be `"y"`, `"s"`, `"ei"` (negative expected improvement), or `"all"`. Default is "y".
214-
kernel (str):
215-
The kernel to be used by the Kriging surrogate model.
216-
Can be either "gauss", "matern32", "matern52", or "exponential". Default is "gauss".
217-
kernel_params (dict):
218-
The parameters for the kernel function. Default is an empty dictionary.
219212
log_every_n_steps (int):
220213
Lightning: How often to log within steps. Default: 50.
221214
log_level (int):
@@ -404,8 +397,6 @@ def fun_control_init(
404397
'horizon': 7,
405398
'infill_criterion': 'y',
406399
'k_folds': None,
407-
'kernel': 'gauss',
408-
'kernel_params': {},
409400
'loss_function': None,
410401
'lower': None,
411402
'max_surrogate_points': 100,
@@ -498,8 +489,6 @@ def fun_control_init(
498489
"hyperdict": hyperdict,
499490
"infill_criterion": infill_criterion,
500491
"k_folds": 3,
501-
"kernel": kernel,
502-
"kernel_params": kernel_params,
503492
"log_every_n_steps": log_every_n_steps,
504493
"log_graph": False,
505494
"log_level": log_level,
@@ -765,6 +754,8 @@ def design_control_init(init_size=10, repeats=1) -> dict:
765754

766755

767756
def surrogate_control_init(
757+
kernel="gauss",
758+
kernel_params={},
768759
log_level: int = 50,
769760
method="regression",
770761
model_optimizer=differential_evolution,
@@ -789,6 +780,11 @@ def surrogate_control_init(
789780
"""Initialize surrogate_control dictionary.
790781
791782
Args:
783+
kernel (str):
784+
The kernel to be used by the Kriging surrogate model.
785+
Can be either "gauss", "matern32", "matern52", or "exponential". Default is "gauss".
786+
kernel_params (dict):
787+
The parameters for the kernel function. Default is an empty dictionary.
792788
model_optimizer (object):
793789
The optimizer object used for the search on surrogate.
794790
Default is differential_evolution.
@@ -871,6 +867,8 @@ def surrogate_control_init(
871867
"min_theta": min_theta,
872868
"max_theta": max_theta,
873869
"isotropic": isotropic,
870+
"kernel": kernel,
871+
"kernel_params": kernel_params,
874872
"p_val": p_val,
875873
"n_p": n_p,
876874
"optim_p": optim_p,

test/test_spot_run_1.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,12 @@
3636
tolerance_x=tolerance_x,
3737
TENSORBOARD_CLEAN=True,
3838
tensorboard_log=True,
39-
kernel=kernel,
40-
kernel_params=kernel_params,
4139
selection_method=selection_method,
4240
min_success_rate=min_success_rate
4341
)
4442
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)
43+
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, kernel=kernel,
44+
kernel_params=kernel_params)
4645

4746
def test_run_1():
4847
spot_exact_y = Spot(

0 commit comments

Comments
 (0)