Skip to content

Commit 5ae5500

Browse files
0.34.5
1 parent 8ee1729 commit 5ae5500

11 files changed

Lines changed: 3241 additions & 1431 deletions

notebooks/00_spotPython_tests.ipynb

Lines changed: 44 additions & 0 deletions
Large diffs are not rendered by default.

notebooks/spot_aquisition_mm_rosenbrock_6d.ipynb

Lines changed: 699 additions & 0 deletions
Large diffs are not rendered by default.

notebooks/spot_aquisition_random_rosenbrock_6d.ipynb

Lines changed: 1387 additions & 0 deletions
Large diffs are not rendered by default.

notebooks/spot_michalewicz_10d.ipynb

Lines changed: 716 additions & 1060 deletions
Large diffs are not rendered by default.

notebooks/spot_rosenbrock_6d.ipynb

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

src/spotpython/spot/spot.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from spotpython.surrogate.kriging import Kriging
1818
from spotpython.utils.repair import apply_penalty_NA
1919
from spotpython.utils.seed import set_all_seeds
20+
from spotpython.utils.sampling import propose_mmphi_intensive_minimizing_point
2021
import numpy as np
2122
import pandas as pd
2223
import pylab
@@ -367,9 +368,11 @@ def _set_additional_attributes(self) -> None:
367368
self.n_points = self.fun_control["n_points"]
368369
self.progress_file = self.fun_control["progress_file"]
369370
self.tkagg = self.fun_control["tkagg"]
371+
# self.success_counter = 0
370372
if self.tkagg:
371373
matplotlib.use("TkAgg")
372374
self.verbosity = self.fun_control["verbosity"]
375+
self.acquisition_failure_strategy = self.fun_control["acquisition_failure_strategy"]
373376
self.max_surrogate_points = self.surrogate_control["max_surrogate_points"]
374377
self.use_nystrom = self.surrogate_control["use_nystrom"]
375378
self.nystrom_m = self.surrogate_control["nystrom_m"]
@@ -870,8 +873,18 @@ def get_new_X0(self) -> np.array:
870873
return repeat(X0, self.fun_repeats, axis=0)
871874
# If no X0 found, then generate self.n_points new solutions:
872875
else:
873-
self.design = SpaceFilling(k=self.k, seed=self.fun_control["seed"] + self.counter)
874-
X0 = self.generate_design(size=self.n_points, repeats=self.design_control["repeats"], lower=self.lower, upper=self.upper)
876+
# No new X0 found on surrogate:
877+
# use morris-mitchell ("mm") or random design as fallback
878+
if self.acquisition_failure_strategy == "mm":
879+
X0 = propose_mmphi_intensive_minimizing_point(X=self.X, n_candidates=1000, q=2, p=2, seed=1, lower=self.lower, upper=self.upper)
880+
# ensure that X0 is repeated according to repeats=self.design_control["repeats"]
881+
X0 = repeat(X0, self.design_control["repeats"], axis=0)
882+
print("Using mmphi minimizing point as fallback.")
883+
else:
884+
# fallback to spacefilling design (acquisition_failure_strategy == "random"):
885+
self.design = SpaceFilling(k=self.k, seed=self.fun_control["seed"] + self.counter)
886+
X0 = self.generate_design(size=self.n_points, repeats=self.design_control["repeats"], lower=self.lower, upper=self.upper)
887+
print("Using spacefilling design as fallback.")
875888
X0 = repair_non_numeric(X0, self.var_type)
876889
logger.warning("No new XO found on surrogate. Generate new solution %s", X0)
877890
return X0
@@ -1578,6 +1591,19 @@ def update_design(self) -> None:
15781591
# Apply penalty for NA values works only on so values:
15791592
y0 = apply_penalty_NA(y0, self.fun_control["penalty_NA"], verbosity=self.verbosity)
15801593
X0, y0 = remove_nan(X0, y0, stop_on_zero_return=False)
1594+
# check if the new y0 value is smaller that the min of the self.y values so far and
1595+
# in this case increase the success_counter. Calculate the success rate, which is defined as
1596+
# the number of successful improvements divided by the total number of function evaluations over
1597+
# the last window_size evaluations:
1598+
# self.success_window_size = 10
1599+
# if y0.shape[0] > 0:
1600+
# for y_val in y0:
1601+
# if y_val < self.min_y:
1602+
# self.success_counter += 1
1603+
# total_evaluations = self.counter + y0.shape[0]
1604+
# window_size = min(total_evaluations, self.success_window_size)
1605+
# self.success_rate = self.success_counter / window_size
1606+
# print(f"Success rate over the last {window_size} evaluations: {self.success_rate:.4f}")
15811607
# Append New Solutions (only if they are not nan):
15821608
if y0.shape[0] > 0:
15831609
self.X = np.append(self.X, X0, axis=0)

src/spotpython/utils/init.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ def fun_control_init(
2727
PREFIX=None,
2828
TENSORBOARD_CLEAN=False,
2929
accelerator="auto",
30+
acquisition_failure_strategy="random",
3031
check_finite=True,
3132
collate_fn_name=None,
3233
converters=None,
@@ -132,6 +133,10 @@ def fun_control_init(
132133
The accelerator to be used by the Lighting Trainer.
133134
It can be either "auto", "dp", "ddp", "ddp2", "ddp_spawn", "ddp_cpu", "gpu", "tpu".
134135
Default is "auto".
136+
acquisition_failure_strategy (str):
137+
Strategy to handle acquisition function failure.
138+
Can be "random" to fall back to random sampling when the acquisition function fails.
139+
Default is "random".
135140
check_finite (bool):
136141
When set True, stops training when the monitor becomes NaN or infinite.
137142
Default is True.
@@ -367,6 +372,7 @@ def fun_control_init(
367372
'_L_out': 11,
368373
'_L_cond': None,
369374
'accelerator': "auto",
375+
'acquisition_failure_strategy': "random",
370376
'check_finite': True,
371377
'core_model': None,
372378
'core_model_name': None,
@@ -440,6 +446,7 @@ def fun_control_init(
440446
"_L_cond": _L_cond,
441447
"_torchmetric": _torchmetric,
442448
"accelerator": accelerator,
449+
"acquisition_failure_strategy": acquisition_failure_strategy,
443450
"check_finite": check_finite,
444451
"collate_fn_name": collate_fn_name,
445452
"converters": converters,

src/spotpython/utils/sampling.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,3 +1007,66 @@ def mmphi_intensive_update(X: np.ndarray, new_point: np.ndarray, J: np.ndarray,
10071007
intensive_phiq = (sum_term / M) ** (1.0 / q)
10081008

10091009
return intensive_phiq, updated_J, updated_d
1010+
1011+
1012+
def propose_mmphi_intensive_minimizing_point(
1013+
X: np.ndarray,
1014+
n_candidates: int = 1000,
1015+
q: float = 2.0,
1016+
p: float = 2.0,
1017+
seed: Optional[int] = None,
1018+
lower: Optional[np.ndarray] = None,
1019+
upper: Optional[np.ndarray] = None,
1020+
) -> np.ndarray:
1021+
"""
1022+
Propose a new point that, when added to X, minimizes the intensive Morris-Mitchell (mmphi_intensive) criterion.
1023+
1024+
Args:
1025+
X (np.ndarray): Existing points, shape (n_points, n_dim).
1026+
n_candidates (int): Number of random candidates to sample.
1027+
q (float): Exponent for mmphi_intensive.
1028+
p (float): Distance norm for mmphi_intensive.
1029+
seed (int, optional): Random seed.
1030+
lower (np.ndarray, optional): Lower bounds for each dimension (default: 0).
1031+
upper (np.ndarray, optional): Upper bounds for each dimension (default: 1).
1032+
1033+
Returns:
1034+
np.ndarray: Proposed new point, shape (1, n_dim).
1035+
1036+
Examples:
1037+
>>> import numpy as np
1038+
from spotpython.utils.sampling import propose_mmphi_intensive_minimizing_point
1039+
# Existing design with 3 points in 2D
1040+
X = np.array([[1.0, 0.0], [0.5, 0.5], [1.0, 1.0]])
1041+
# Propose a new point
1042+
new_point = propose_mmphi_intensive_minimizing_point (X, n_candidates=500, q=2, p=2, seed=42)
1043+
print(new_point)
1044+
# plot the existing points and the new proposed point
1045+
import matplotlib.pyplot as plt
1046+
plt.scatter(X[:, 0], X[:, 1], color='blue', label='Existing Points')
1047+
plt.scatter(new_point[0, 0], new_point[0, 1], color='red', label='Proposed Point')
1048+
plt.legend()
1049+
# add grid and labels
1050+
plt.grid()
1051+
plt.title('MM-PHI Proposed Point')
1052+
plt.xlabel('X1')
1053+
plt.ylabel('X2')
1054+
plt.show()
1055+
"""
1056+
rng = np.random.default_rng(seed)
1057+
n_dim = X.shape[1]
1058+
if lower is None:
1059+
lower = np.zeros(n_dim)
1060+
if upper is None:
1061+
upper = np.ones(n_dim)
1062+
# Generate candidate points uniformly
1063+
candidates = rng.uniform(lower, upper, size=(n_candidates, n_dim))
1064+
best_phi = np.inf
1065+
best_point = None
1066+
for cand in candidates:
1067+
X_aug = np.vstack([X, cand])
1068+
phi, _, _ = mmphi_intensive(X_aug, q=q, p=p)
1069+
if phi < best_phi:
1070+
best_phi = phi
1071+
best_point = cand
1072+
return best_point.reshape(1, -1)

test/test_get_spot_attributes_as_df.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ def test_get_spot_attributes_as_df():
3434

3535
# Define expected attribute names (ensure these match your Spot class' attributes)
3636
expected_attributes = ['X',
37+
'acquisition_failure_strategy',
3738
'all_lower',
3839
'all_upper',
3940
'all_var_name',

0 commit comments

Comments
 (0)