Skip to content

Commit dc85286

Browse files
0.34.3
spot accepts use_nystrom via surrogate_control init
1 parent e608329 commit dc85286

10 files changed

Lines changed: 204 additions & 9 deletions

notebooks/00_spotPython_tests.ipynb

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

src/spotpython/fun/objectivefunctions.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -676,3 +676,93 @@ def fun_random_error(self, X: np.ndarray, fun_control: Optional[Dict] = None) ->
676676
y[nan_mask] = np.nan
677677

678678
return self._add_noise(y)
679+
680+
def fun_ackley(self, X: np.ndarray, fun_control: Optional[Dict] = None) -> np.ndarray:
681+
"""
682+
Ackley function.
683+
Global minimum at x_i = 0 for all i, f(x*) = 0.
684+
Typical domain: x_i in [-32.768, 32.768] for all i.
685+
686+
Args:
687+
X (np.ndarray): Input array of shape (n_samples, n_features).
688+
fun_control (dict, optional): Control dict for noise etc.
689+
690+
Returns:
691+
np.ndarray: Function values of shape (n_samples,).
692+
693+
Examples:
694+
>>> from spotpython.fun.objectivefunctions import Analytical
695+
>>> import numpy as np
696+
>>> X = np.zeros((2, 3))
697+
>>> fun = Analytical()
698+
>>> fun.fun_ackley(X)
699+
array([0., 0.])
700+
"""
701+
X = self._prepare_input_data(X, fun_control)
702+
a = 20
703+
b = 0.2
704+
c = 2 * np.pi
705+
n_dim = X.shape[1]
706+
sum_sq = np.sum(X**2, axis=1)
707+
sum_cos = np.sum(np.cos(c * X), axis=1)
708+
term1 = -a * np.exp(-b * np.sqrt(sum_sq / n_dim))
709+
term2 = -np.exp(sum_cos / n_dim)
710+
y = term1 + term2 + a + np.exp(1)
711+
return self._add_noise(y)
712+
713+
def fun_michalewicz(self, X: np.ndarray, fun_control: Optional[Dict] = None) -> np.ndarray:
714+
"""
715+
Michalewicz function.
716+
Global minimum depends on dimension, typical domain: x_i in [0, pi].
717+
Default m=10 as in the reference.
718+
719+
Args:
720+
X (np.ndarray): Input array of shape (n_samples, n_features).
721+
fun_control (dict, optional): Control dict for noise etc. Can set 'm'.
722+
723+
Returns:
724+
np.ndarray: Function values of shape (n_samples,).
725+
726+
Examples:
727+
>>> from spotpython.fun.objectivefunctions import Analytical
728+
>>> import numpy as np
729+
>>> X = np.array([[2.20, 1.57], [2.20, 1.20]])
730+
>>> fun = Analytical()
731+
>>> fun.fun_michalewicz(X)
732+
array([-1.8013..., -1.5274...])
733+
"""
734+
X = self._prepare_input_data(X, fun_control)
735+
m = 10
736+
if fun_control is not None and "m" in fun_control:
737+
m = fun_control["m"]
738+
i = np.arange(1, X.shape[1] + 1)
739+
# Broadcasting: (n_samples, n_features)
740+
y = -np.sum(np.sin(X) * (np.sin(i * X**2 / np.pi)) ** (2 * m), axis=1)
741+
return self._add_noise(y)
742+
743+
def fun_rosenbrock(self, X: np.ndarray, fun_control: Optional[Dict] = None) -> np.ndarray:
744+
"""
745+
Rosenbrock function (general n-dim).
746+
Global minimum at x_i = 1 for all i, f(x*) = 0.
747+
Typical domain: x_i in [-5, 10].
748+
749+
Args:
750+
X (np.ndarray): Input array of shape (n_samples, n_features).
751+
fun_control (dict, optional): Control dict for noise etc.
752+
753+
Returns:
754+
np.ndarray: Function values of shape (n_samples,).
755+
756+
Examples:
757+
>>> from spotpython.fun.objectivefunctions import Analytical
758+
>>> import numpy as np
759+
>>> X = np.ones((2, 3))
760+
>>> fun = Analytical()
761+
>>> fun.fun_rosenbrock(X)
762+
array([0., 0.])
763+
"""
764+
X = self._prepare_input_data(X, fun_control)
765+
b = 100
766+
# Rosenbrock sum over d-1 dimensions: sum_{i=1}^{d-1} [b*(x_{i+1} - x_i^2)^2 + (x_i - 1)^2]
767+
y = np.sum(b * (X[:, 1:] - X[:, :-1] ** 2) ** 2 + (X[:, :-1] - 1) ** 2, axis=1)
768+
return self._add_noise(y)

src/spotpython/spot/spot.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ def _set_var_name(self) -> None:
352352

353353
def _set_additional_attributes(self) -> None:
354354
"""
355-
Set additional attributes based on the fun_control dictionary
355+
Set additional attributes based on the fun_control or surrogate_control dictionary
356356
"""
357357
self.fun_evals = self.fun_control["fun_evals"]
358358
self.fun_repeats = self.fun_control["fun_repeats"]
@@ -365,12 +365,15 @@ def _set_additional_attributes(self) -> None:
365365
self.show_progress = self.fun_control["show_progress"]
366366
self.infill_criterion = self.fun_control["infill_criterion"]
367367
self.n_points = self.fun_control["n_points"]
368-
self.max_surrogate_points = self.fun_control["max_surrogate_points"]
369368
self.progress_file = self.fun_control["progress_file"]
370369
self.tkagg = self.fun_control["tkagg"]
371370
if self.tkagg:
372371
matplotlib.use("TkAgg")
373372
self.verbosity = self.fun_control["verbosity"]
373+
self.max_surrogate_points = self.surrogate_control["max_surrogate_points"]
374+
self.use_nystrom = self.surrogate_control["use_nystrom"]
375+
self.nystrom_m = self.surrogate_control["nystrom_m"]
376+
self.nystrom_seed = self.surrogate_control["nystrom_seed"]
374377

375378
# Internal attributes:
376379
self.X = None
@@ -449,6 +452,9 @@ def surrogate_setup(self, surrogate) -> None:
449452
- optim_p: Whether to optimize p parameters
450453
- min/max_Lambda: Bounds for lambda parameters
451454
- metric_factorial: Metric for factorial parameters
455+
- use_nystrom: Whether to use Nystrom approximation
456+
- nystrom_m: Number of Nystrom points
457+
- nystrom_seed: Seed for Nystrom approximation
452458
453459
Examples:
454460
>>> import numpy as np
@@ -511,6 +517,9 @@ def surrogate_setup(self, surrogate) -> None:
511517
spot_writer=self.spot_writer,
512518
counter=self.design_control["init_size"] * self.design_control["repeats"] - 1,
513519
metric_factorial=self.surrogate_control["metric_factorial"],
520+
use_nystrom=self.surrogate_control["use_nystrom"],
521+
nystrom_m=self.surrogate_control["nystrom_m"],
522+
nystrom_seed=self.surrogate_control["nystrom_seed"],
514523
)
515524

516525
def get_spot_attributes_as_df(self) -> pd.DataFrame:
@@ -1339,7 +1348,7 @@ def fit_surrogate(self) -> None:
13391348
surrogate model `surrogate`. The default surrogate model is
13401349
an instance from spotpython's `Kriging` class.
13411350
If `show_models` is `True`, the model is plotted.
1342-
If the number of points is greater than `max_surrogate_points`,
1351+
If the number of points is greater than `max_surrogate_points` and `use_nyström` is `False`,
13431352
the surrogate model is fitted to a subset of the data points.
13441353
The subset is selected using the `select_distant_points()` function.
13451354
@@ -1398,7 +1407,7 @@ def fit_surrogate(self) -> None:
13981407
X_points = self.X.shape[0]
13991408
y_points = self.y.shape[0]
14001409
if X_points == y_points:
1401-
if X_points > self.max_surrogate_points:
1410+
if (X_points > self.max_surrogate_points) and (self.use_nystrom is False):
14021411
logger.info("Selecting distant points for surrogate fitting.")
14031412
X_S, y_S = select_distant_points(X=self.X, y=self.y, k=self.max_surrogate_points)
14041413
else:

src/spotpython/utils/init.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ def fun_control_init(
218218
max_time (int):
219219
The maximum time in minutes.
220220
max_surrogate_points (int):
221-
The maximum number of points in the surrogate model. Default is inf.
221+
The maximum number of points in the surrogate model. Has no effect. Moved to surrogate_control_init().
222222
metric_sklearn (object):
223223
The metric object from the scikit-learn library. Default is None.
224224
metric_sklearn_name (str):
@@ -749,6 +749,10 @@ def surrogate_control_init(
749749
theta_init_zero=False,
750750
var_type=None,
751751
metric_factorial="canberra",
752+
use_nystrom=False,
753+
nystrom_m=20,
754+
nystrom_seed=1234,
755+
max_surrogate_points=30,
752756
) -> dict:
753757
"""Initialize surrogate_control dictionary.
754758
@@ -793,6 +797,14 @@ def surrogate_control_init(
793797
Note: Will be set in the Spot class.
794798
metric_factorial (str):
795799
The metric to be used for the factorial design. Default is "canberra".
800+
use_nystrom (bool):
801+
Whether to use the Nystrom approximation or not. Default is False.
802+
nystrom_m (int):
803+
The number of Nystrom points to be used. Default is 20.
804+
nystrom_seed (int):
805+
The seed to use for the Nystrom approximation. Default is 1234.
806+
max_surrogate_points (int):
807+
The maximum number of points in the surrogate model. Has only an effect if use_nystrom is False. Default is 30.
796808
797809
Returns:
798810
surrogate_control (dict):
@@ -836,6 +848,10 @@ def surrogate_control_init(
836848
"theta_init_zero": theta_init_zero,
837849
"var_type": var_type,
838850
"metric_factorial": metric_factorial,
851+
"use_nystrom": use_nystrom,
852+
"nystrom_m": nystrom_m,
853+
"nystrom_seed": nystrom_seed,
854+
"max_surrogate_points": max_surrogate_points,
839855
}
840856
return surrogate_control
841857

test/test_ackley.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import numpy as np
2+
import pytest
3+
from spotpython.fun.objectivefunctions import Analytical
4+
5+
def test_fun_ackley_basic():
6+
fun = Analytical()
7+
# Test at the global minimum (should be close to 0)
8+
X = np.zeros((3, 5))
9+
y = fun.fun_ackley(X)
10+
assert np.allclose(y, 0, atol=1e-7)
11+
12+
def test_fun_ackley_typical_domain():
13+
fun = Analytical()
14+
# Test at a random point in the typical domain
15+
X = np.array([[1.0, 2.0, 3.0], [-10.0, 0.0, 10.0]])
16+
y = fun.fun_ackley(X)
17+
assert y.shape == (2,)
18+
assert np.all(np.isfinite(y))
19+
20+
def test_fun_ackley_noise():
21+
fun = Analytical(sigma=0.5, seed=42)
22+
X = np.zeros((5, 2))
23+
y = fun.fun_ackley(X)
24+
# Should not be exactly zero due to noise
25+
assert not np.allclose(y, 0)
26+
assert y.shape == (5,)

test/test_get_spot_attributes_as_df.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ def test_get_spot_attributes_as_df():
6161
'min_y',
6262
'n_points',
6363
'noise',
64+
'nystrom_m',
65+
'nystrom_seed',
6466
'ocba_delta',
6567
'optimizer_control',
6668
'progress_file',
@@ -74,6 +76,7 @@ def test_get_spot_attributes_as_df():
7476
'tkagg',
7577
'tolerance_x',
7678
'upper',
79+
'use_nystrom',
7780
'var_name',
7881
'var_type',
7982
'var_y',

test/test_michalewicz.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import numpy as np
2+
import pytest
3+
from spotpython.fun.objectivefunctions import Analytical
4+
5+
def test_fun_michalewicz_global_minimum():
6+
fun = Analytical()
7+
# Known global minimum for d=2 is at approx [2.20, 1.57], value ≈ -1.8013
8+
X = np.array([[2.20, 1.57]])
9+
y = fun.fun_michalewicz(X)
10+
assert np.allclose(y, -1.8013, atol=1e-3)
11+
12+
def test_fun_michalewicz_shape_and_finiteness():
13+
fun = Analytical()
14+
X = np.array([[1.0, 2.0], [0.5, 1.0]])
15+
y = fun.fun_michalewicz(X)
16+
assert y.shape == (2,)
17+
assert np.all(np.isfinite(y))
18+
19+
def test_fun_michalewicz_noise():
20+
fun = Analytical(sigma=0.5, seed=42)
21+
X = np.array([[2.20, 1.57], [1.0, 2.0]])
22+
y = fun.fun_michalewicz(X)
23+
# Should not be exactly the noiseless value
24+
assert not np.allclose(y, fun.fun_michalewicz(X, fun_control={"sigma": 0.0, "seed": 42}))
25+
assert y.shape == (2,)

test/test_rosenbrock.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import numpy as np
2+
import pytest
3+
from spotpython.fun.objectivefunctions import Analytical
4+
5+
def test_fun_rosenbrock_global_minimum():
6+
fun = Analytical()
7+
# Global minimum at x_i = 1 for all i, f(x*) = 0
8+
X = np.ones((4, 3))
9+
y = fun.fun_rosenbrock(X)
10+
assert np.allclose(y, 0, atol=1e-8)
11+
12+
def test_fun_rosenbrock_typical_values():
13+
fun = Analytical()
14+
X = np.array([[0, 0, 0], [1, 2, 3], [-1, -1, -1]])
15+
y = fun.fun_rosenbrock(X)
16+
assert y.shape == (3,)
17+
assert np.all(np.isfinite(y))
18+
19+
def test_fun_rosenbrock_noise():
20+
fun = Analytical(sigma=0.5, seed=123)
21+
X = np.ones((5, 2))
22+
y = fun.fun_rosenbrock(X)
23+
# Should not be exactly zero due to noise
24+
assert not np.allclose(y, 0)
25+
assert y.shape == (5,)

test/test_show_progress.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ def test_show_progress():
99
from spotpython.utils.init import (
1010
fun_control_init,
1111
design_control_init,
12+
surrogate_control_init
1213
)
1314

1415
# number of initial points:

0 commit comments

Comments
 (0)