bayesian-optimization 2.0.2__tar.gz → 3.0.0b1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (17) hide show
  1. {bayesian_optimization-2.0.2 → bayesian_optimization-3.0.0b1}/PKG-INFO +16 -2
  2. {bayesian_optimization-2.0.2 → bayesian_optimization-3.0.0b1}/README.md +13 -0
  3. {bayesian_optimization-2.0.2 → bayesian_optimization-3.0.0b1}/bayes_opt/acquisition.py +56 -34
  4. {bayesian_optimization-2.0.2 → bayesian_optimization-3.0.0b1}/bayes_opt/bayesian_optimization.py +34 -35
  5. {bayesian_optimization-2.0.2 → bayesian_optimization-3.0.0b1}/bayes_opt/constraint.py +4 -1
  6. {bayesian_optimization-2.0.2 → bayesian_optimization-3.0.0b1}/bayes_opt/domain_reduction.py +14 -7
  7. {bayesian_optimization-2.0.2 → bayesian_optimization-3.0.0b1}/bayes_opt/logger.py +11 -8
  8. bayesian_optimization-3.0.0b1/bayes_opt/parameter.py +506 -0
  9. {bayesian_optimization-2.0.2 → bayesian_optimization-3.0.0b1}/bayes_opt/target_space.py +235 -65
  10. {bayesian_optimization-2.0.2 → bayesian_optimization-3.0.0b1}/pyproject.toml +5 -2
  11. {bayesian_optimization-2.0.2 → bayesian_optimization-3.0.0b1}/LICENSE +0 -0
  12. {bayesian_optimization-2.0.2 → bayesian_optimization-3.0.0b1}/bayes_opt/__init__.py +0 -0
  13. {bayesian_optimization-2.0.2 → bayesian_optimization-3.0.0b1}/bayes_opt/event.py +0 -0
  14. {bayesian_optimization-2.0.2 → bayesian_optimization-3.0.0b1}/bayes_opt/exception.py +0 -0
  15. {bayesian_optimization-2.0.2 → bayesian_optimization-3.0.0b1}/bayes_opt/observer.py +0 -0
  16. {bayesian_optimization-2.0.2 → bayesian_optimization-3.0.0b1}/bayes_opt/py.typed +0 -0
  17. {bayesian_optimization-2.0.2 → bayesian_optimization-3.0.0b1}/bayes_opt/util.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: bayesian-optimization
3
- Version: 2.0.2
3
+ Version: 3.0.0b1
4
4
  Summary: Bayesian Optimization package
5
5
  License: MIT
6
6
  Author: Fernando Nogueira
@@ -15,7 +15,8 @@ Classifier: Programming Language :: Python :: 3.13
15
15
  Requires-Dist: colorama (>=0.4.6,<0.5.0)
16
16
  Requires-Dist: numpy (>=1.25)
17
17
  Requires-Dist: scikit-learn (>=1.0.0,<2.0.0)
18
- Requires-Dist: scipy (>=1.0.0,<2.0.0)
18
+ Requires-Dist: scipy (>=1.0.0,<2.0.0) ; python_version < "3.13"
19
+ Requires-Dist: scipy (>=1.14.1,<2.0.0) ; python_version >= "3.13"
19
20
  Description-Content-Type: text/markdown
20
21
 
21
22
  <div align="center">
@@ -206,3 +207,16 @@ For constrained optimization:
206
207
  }
207
208
  ```
208
209
 
210
+ For optimization over non-float parameters:
211
+ ```
212
+ @article{garrido2020dealing,
213
+ title={Dealing with categorical and integer-valued variables in bayesian optimization with gaussian processes},
214
+ author={Garrido-Merch{\'a}n, Eduardo C and Hern{\'a}ndez-Lobato, Daniel},
215
+ journal={Neurocomputing},
216
+ volume={380},
217
+ pages={20--35},
218
+ year={2020},
219
+ publisher={Elsevier}
220
+ }
221
+ ```
222
+
@@ -185,3 +185,16 @@ For constrained optimization:
185
185
  year={2014}
186
186
  }
187
187
  ```
188
+
189
+ For optimization over non-float parameters:
190
+ ```
191
+ @article{garrido2020dealing,
192
+ title={Dealing with categorical and integer-valued variables in bayesian optimization with gaussian processes},
193
+ author={Garrido-Merch{\'a}n, Eduardo C and Hern{\'a}ndez-Lobato, Daniel},
194
+ journal={Neurocomputing},
195
+ volume={380},
196
+ pages={20--35},
197
+ year={2020},
198
+ publisher={Elsevier}
199
+ }
200
+ ```
@@ -127,7 +127,7 @@ class AcquisitionFunction(abc.ABC):
127
127
  self._fit_gp(gp=gp, target_space=target_space)
128
128
 
129
129
  acq = self._get_acq(gp=gp, constraint=target_space.constraint)
130
- return self._acq_min(acq, target_space.bounds, n_random=n_random, n_l_bfgs_b=n_l_bfgs_b)
130
+ return self._acq_min(acq, target_space, n_random=n_random, n_l_bfgs_b=n_l_bfgs_b)
131
131
 
132
132
  def _get_acq(
133
133
  self, gp: GaussianProcessRegressor, constraint: ConstraintModel | None = None
@@ -182,7 +182,7 @@ class AcquisitionFunction(abc.ABC):
182
182
  def _acq_min(
183
183
  self,
184
184
  acq: Callable[[NDArray[Float]], NDArray[Float]],
185
- bounds: NDArray[Float],
185
+ space: TargetSpace,
186
186
  n_random: int = 10_000,
187
187
  n_l_bfgs_b: int = 10,
188
188
  ) -> NDArray[Float]:
@@ -197,10 +197,8 @@ class AcquisitionFunction(abc.ABC):
197
197
  acq : Callable
198
198
  Acquisition function to use. Should accept an array of parameters `x`.
199
199
 
200
- bounds : np.ndarray
201
- Bounds of the search space. For `N` parameters this has shape
202
- `(N, 2)` with `[i, 0]` the lower bound of parameter `i` and
203
- `[i, 1]` the upper bound.
200
+ space : TargetSpace
201
+ The target space over which to optimize.
204
202
 
205
203
  n_random : int
206
204
  Number of random samples to use.
@@ -217,15 +215,22 @@ class AcquisitionFunction(abc.ABC):
217
215
  if n_random == 0 and n_l_bfgs_b == 0:
218
216
  error_msg = "Either n_random or n_l_bfgs_b needs to be greater than 0."
219
217
  raise ValueError(error_msg)
220
- x_min_r, min_acq_r = self._random_sample_minimize(acq, bounds, n_random=n_random)
221
- x_min_l, min_acq_l = self._l_bfgs_b_minimize(acq, bounds, n_x_seeds=n_l_bfgs_b)
222
- # Either n_random or n_l_bfgs_b is not 0 => at least one of x_min_r and x_min_l is not None
223
- if min_acq_r < min_acq_l:
224
- return x_min_r
225
- return x_min_l
218
+ x_min_r, min_acq_r, x_seeds = self._random_sample_minimize(
219
+ acq, space, n_random=max(n_random, n_l_bfgs_b), n_x_seeds=n_l_bfgs_b
220
+ )
221
+ if n_l_bfgs_b:
222
+ x_min_l, min_acq_l = self._l_bfgs_b_minimize(acq, space, x_seeds=x_seeds)
223
+ # Either n_random or n_l_bfgs_b is not 0 => at least one of x_min_r and x_min_l is not None
224
+ if min_acq_r > min_acq_l:
225
+ return x_min_l
226
+ return x_min_r
226
227
 
227
228
  def _random_sample_minimize(
228
- self, acq: Callable[[NDArray[Float]], NDArray[Float]], bounds: NDArray[Float], n_random: int
229
+ self,
230
+ acq: Callable[[NDArray[Float]], NDArray[Float]],
231
+ space: TargetSpace,
232
+ n_random: int,
233
+ n_x_seeds: int = 0,
229
234
  ) -> tuple[NDArray[Float] | None, float]:
230
235
  """Random search to find the minimum of `acq` function.
231
236
 
@@ -234,14 +239,14 @@ class AcquisitionFunction(abc.ABC):
234
239
  acq : Callable
235
240
  Acquisition function to use. Should accept an array of parameters `x`.
236
241
 
237
- bounds : np.ndarray
238
- Bounds of the search space. For `N` parameters this has shape
239
- `(N, 2)` with `[i, 0]` the lower bound of parameter `i` and
240
- `[i, 1]` the upper bound.
242
+ space : TargetSpace
243
+ The target space over which to optimize.
241
244
 
242
245
  n_random : int
243
246
  Number of random samples to use.
244
247
 
248
+ n_x_seeds : int
249
+ Number of top points to return, for use as starting points for L-BFGS-B.
245
250
  Returns
246
251
  -------
247
252
  x_min : np.ndarray
@@ -252,14 +257,22 @@ class AcquisitionFunction(abc.ABC):
252
257
  """
253
258
  if n_random == 0:
254
259
  return None, np.inf
255
- x_tries = self.random_state.uniform(bounds[:, 0], bounds[:, 1], size=(n_random, bounds.shape[0]))
260
+ x_tries = space.random_sample(n_random, random_state=self.random_state)
256
261
  ys = acq(x_tries)
257
262
  x_min = x_tries[ys.argmin()]
258
263
  min_acq = ys.min()
259
- return x_min, min_acq
264
+ if n_x_seeds != 0:
265
+ idxs = np.argsort(ys)[-n_x_seeds:]
266
+ x_seeds = x_tries[idxs]
267
+ else:
268
+ x_seeds = []
269
+ return x_min, min_acq, x_seeds
260
270
 
261
271
  def _l_bfgs_b_minimize(
262
- self, acq: Callable[[NDArray[Float]], NDArray[Float]], bounds: NDArray[Float], n_x_seeds: int = 10
272
+ self,
273
+ acq: Callable[[NDArray[Float]], NDArray[Float]],
274
+ space: TargetSpace,
275
+ x_seeds: NDArray[Float] | None = None,
263
276
  ) -> tuple[NDArray[Float] | None, float]:
264
277
  """Random search to find the minimum of `acq` function.
265
278
 
@@ -268,13 +281,11 @@ class AcquisitionFunction(abc.ABC):
268
281
  acq : Callable
269
282
  Acquisition function to use. Should accept an array of parameters `x`.
270
283
 
271
- bounds : np.ndarray
272
- Bounds of the search space. For `N` parameters this has shape
273
- `(N, 2)` with `[i, 0]` the lower bound of parameter `i` and
274
- `[i, 1]` the upper bound.
284
+ space : TargetSpace
285
+ The target space over which to optimize.
275
286
 
276
- n_x_seeds : int
277
- Number of starting points for the L-BFGS-B optimizer.
287
+ x_seeds : int
288
+ Starting points for the L-BFGS-B optimizer.
278
289
 
279
290
  Returns
280
291
  -------
@@ -284,33 +295,44 @@ class AcquisitionFunction(abc.ABC):
284
295
  min_acq : float
285
296
  Acquisition function value at `x_min`
286
297
  """
287
- if n_x_seeds == 0:
288
- return None, np.inf
289
- x_seeds = self.random_state.uniform(bounds[:, 0], bounds[:, 1], size=(n_x_seeds, bounds.shape[0]))
298
+ continuous_dimensions = space.continuous_dimensions
299
+ continuous_bounds = space.bounds[continuous_dimensions]
300
+
301
+ if not continuous_dimensions.any():
302
+ min_acq = np.inf
303
+ x_min = np.array([np.nan] * space.bounds.shape[0])
304
+ return x_min, min_acq
290
305
 
291
306
  min_acq: float | None = None
292
307
  x_try: NDArray[Float]
293
308
  x_min: NDArray[Float]
294
309
  for x_try in x_seeds:
295
- # Find the minimum of minus the acquisition function
296
- res: OptimizeResult = minimize(acq, x_try, bounds=bounds, method="L-BFGS-B")
297
310
 
311
+ def continuous_acq(x: NDArray[Float], x_try=x_try) -> NDArray[Float]:
312
+ x_try[continuous_dimensions] = x
313
+ return acq(x_try)
314
+
315
+ # Find the minimum of minus the acquisition function
316
+ res: OptimizeResult = minimize(
317
+ continuous_acq, x_try[continuous_dimensions], bounds=continuous_bounds, method="L-BFGS-B"
318
+ )
298
319
  # See if success
299
320
  if not res.success:
300
321
  continue
301
322
 
302
323
  # Store it if better than previous minimum(maximum).
303
324
  if min_acq is None or np.squeeze(res.fun) >= min_acq:
304
- x_min = res.x
325
+ x_try[continuous_dimensions] = res.x
326
+ x_min = x_try
305
327
  min_acq = np.squeeze(res.fun)
306
328
 
307
329
  if min_acq is None:
308
330
  min_acq = np.inf
309
- x_min = np.array([np.nan] * bounds.shape[0])
331
+ x_min = np.array([np.nan] * space.bounds.shape[0])
310
332
 
311
333
  # Clip output to make sure it lies within the bounds. Due to floating
312
334
  # point technicalities this is not always the case.
313
- return np.clip(x_min, bounds[:, 0], bounds[:, 1]), min_acq
335
+ return np.clip(x_min, space.bounds[:, 0], space.bounds[:, 1]), min_acq
314
336
 
315
337
 
316
338
  class UpperConfidenceBound(AcquisitionFunction):
@@ -16,13 +16,15 @@ from sklearn.gaussian_process.kernels import Matern
16
16
 
17
17
  from bayes_opt import acquisition
18
18
  from bayes_opt.constraint import ConstraintModel
19
+ from bayes_opt.domain_reduction import DomainTransformer
19
20
  from bayes_opt.event import DEFAULT_EVENTS, Events
20
21
  from bayes_opt.logger import _get_default_logger
22
+ from bayes_opt.parameter import wrap_kernel
21
23
  from bayes_opt.target_space import TargetSpace
22
24
  from bayes_opt.util import ensure_rng
23
25
 
24
26
  if TYPE_CHECKING:
25
- from collections.abc import Callable, Iterable, Mapping, Sequence
27
+ from collections.abc import Callable, Iterable, Mapping
26
28
 
27
29
  from numpy.random import RandomState
28
30
  from numpy.typing import NDArray
@@ -31,6 +33,7 @@ if TYPE_CHECKING:
31
33
  from bayes_opt.acquisition import AcquisitionFunction
32
34
  from bayes_opt.constraint import ConstraintModel
33
35
  from bayes_opt.domain_reduction import DomainTransformer
36
+ from bayes_opt.parameter import BoundsMapping, ParamsType
34
37
 
35
38
  Float = np.floating[Any]
36
39
 
@@ -114,7 +117,7 @@ class BayesianOptimization(Observable):
114
117
  ):
115
118
  self._random_state = ensure_rng(random_state)
116
119
  self._allow_duplicate_points = allow_duplicate_points
117
- self._queue: deque[Mapping[str, float] | Sequence[float] | NDArray[Float]] = deque()
120
+ self._queue: deque[ParamsType] = deque()
118
121
 
119
122
  if acquisition_function is None:
120
123
  if constraint is None:
@@ -128,15 +131,6 @@ class BayesianOptimization(Observable):
128
131
  else:
129
132
  self._acquisition_function = acquisition_function
130
133
 
131
- # Internal GP regressor
132
- self._gp = GaussianProcessRegressor(
133
- kernel=Matern(nu=2.5),
134
- alpha=1e-6,
135
- normalize_y=True,
136
- n_restarts_optimizer=5,
137
- random_state=self._random_state,
138
- )
139
-
140
134
  if constraint is None:
141
135
  # Data structure containing the function to be optimized, the
142
136
  # bounds of its domain, and a record of the evaluations we have
@@ -158,14 +152,22 @@ class BayesianOptimization(Observable):
158
152
  )
159
153
  self.is_constrained = True
160
154
 
155
+ # Internal GP regressor
156
+ self._gp = GaussianProcessRegressor(
157
+ kernel=wrap_kernel(Matern(nu=2.5), transform=self._space.kernel_transform),
158
+ alpha=1e-6,
159
+ normalize_y=True,
160
+ n_restarts_optimizer=5,
161
+ random_state=self._random_state,
162
+ )
163
+
161
164
  self._verbose = verbose
162
165
  self._bounds_transformer = bounds_transformer
163
166
  if self._bounds_transformer:
164
- try:
165
- self._bounds_transformer.initialize(self._space)
166
- except (AttributeError, TypeError) as exc:
167
- error_msg = "The transformer must be an instance of DomainTransformer"
168
- raise TypeError(error_msg) from exc
167
+ if not isinstance(self._bounds_transformer, DomainTransformer):
168
+ msg = "The transformer must be an instance of DomainTransformer"
169
+ raise TypeError(msg)
170
+ self._bounds_transformer.initialize(self._space)
169
171
 
170
172
  self._sorting_warning_already_shown = False # TODO: remove in future version
171
173
  super().__init__(events=DEFAULT_EVENTS)
@@ -204,10 +206,7 @@ class BayesianOptimization(Observable):
204
206
  return self._space.res()
205
207
 
206
208
  def register(
207
- self,
208
- params: Mapping[str, float] | Sequence[float] | NDArray[Float],
209
- target: float,
210
- constraint_value: float | NDArray[Float] | None = None,
209
+ self, params: ParamsType, target: float, constraint_value: float | NDArray[Float] | None = None
211
210
  ) -> None:
212
211
  """Register an observation with known target.
213
212
 
@@ -225,10 +224,10 @@ class BayesianOptimization(Observable):
225
224
  # TODO: remove in future version
226
225
  if isinstance(params, np.ndarray) and not self._sorting_warning_already_shown:
227
226
  msg = (
228
- "You're attempting to register an np.ndarray. Currently, the optimizer internally sorts"
229
- " parameters by key and expects any registered array to respect this order. In future"
230
- " versions this behaviour will change and the order as given by the pbounds dictionary"
231
- " will be used. If you wish to retain sorted parameters, please manually sort your pbounds"
227
+ "You're attempting to register an np.ndarray. In previous versions, the optimizer internally"
228
+ " sorted parameters by key and expected any registered array to respect this order."
229
+ " In the current and any future version the order as given by the pbounds dictionary will be"
230
+ " used. If you wish to retain sorted parameters, please manually sort your pbounds"
232
231
  " dictionary before constructing the optimizer."
233
232
  )
234
233
  warn(msg, stacklevel=1)
@@ -236,9 +235,7 @@ class BayesianOptimization(Observable):
236
235
  self._space.register(params, target, constraint_value)
237
236
  self.dispatch(Events.OPTIMIZATION_STEP)
238
237
 
239
- def probe(
240
- self, params: Mapping[str, float] | Sequence[float] | NDArray[Float], lazy: bool = True
241
- ) -> None:
238
+ def probe(self, params: ParamsType, lazy: bool = True) -> None:
242
239
  """Evaluate the function at the given points.
243
240
 
244
241
  Useful to guide the optimizer.
@@ -255,10 +252,10 @@ class BayesianOptimization(Observable):
255
252
  # TODO: remove in future version
256
253
  if isinstance(params, np.ndarray) and not self._sorting_warning_already_shown:
257
254
  msg = (
258
- "You're attempting to register an np.ndarray. Currently, the optimizer internally sorts"
259
- " parameters by key and expects any registered array to respect this order. In future"
260
- " versions this behaviour will change and the order as given by the pbounds dictionary"
261
- " will be used. If you wish to retain sorted parameters, please manually sort your pbounds"
255
+ "You're attempting to register an np.ndarray. In previous versions, the optimizer internally"
256
+ " sorted parameters by key and expected any registered array to respect this order."
257
+ " In the current and any future version the order as given by the pbounds dictionary will be"
258
+ " used. If you wish to retain sorted parameters, please manually sort your pbounds"
262
259
  " dictionary before constructing the optimizer."
263
260
  )
264
261
  warn(msg, stacklevel=1)
@@ -270,10 +267,10 @@ class BayesianOptimization(Observable):
270
267
  self._space.probe(params)
271
268
  self.dispatch(Events.OPTIMIZATION_STEP)
272
269
 
273
- def suggest(self) -> dict[str, float]:
270
+ def suggest(self) -> dict[str, float | NDArray[Float]]:
274
271
  """Suggest a promising point to probe next."""
275
272
  if len(self._space) == 0:
276
- return self._space.array_to_params(self._space.random_sample())
273
+ return self._space.array_to_params(self._space.random_sample(random_state=self._random_state))
277
274
 
278
275
  # Finding argmax of the acquisition function.
279
276
  suggestion = self._acquisition_function.suggest(gp=self._gp, target_space=self._space, fit_gp=True)
@@ -292,7 +289,7 @@ class BayesianOptimization(Observable):
292
289
  init_points = max(init_points, 1)
293
290
 
294
291
  for _ in range(init_points):
295
- sample = self._space.random_sample()
292
+ sample = self._space.random_sample(random_state=self._random_state)
296
293
  self._queue.append(self._space.array_to_params(sample))
297
294
 
298
295
  def _prime_subscriptions(self) -> None:
@@ -344,7 +341,7 @@ class BayesianOptimization(Observable):
344
341
 
345
342
  self.dispatch(Events.OPTIMIZATION_END)
346
343
 
347
- def set_bounds(self, new_bounds: Mapping[str, NDArray[Float] | Sequence[float]]) -> None:
344
+ def set_bounds(self, new_bounds: BoundsMapping) -> None:
348
345
  """Modify the bounds of the search space.
349
346
 
350
347
  Parameters
@@ -356,4 +353,6 @@ class BayesianOptimization(Observable):
356
353
 
357
354
  def set_gp_params(self, **params: Any) -> None:
358
355
  """Set parameters of the internal Gaussian Process Regressor."""
356
+ if "kernel" in params:
357
+ params["kernel"] = wrap_kernel(kernel=params["kernel"], transform=self._space.kernel_transform)
359
358
  self._gp.set_params(**params)
@@ -9,6 +9,8 @@ from scipy.stats import norm
9
9
  from sklearn.gaussian_process import GaussianProcessRegressor
10
10
  from sklearn.gaussian_process.kernels import Matern
11
11
 
12
+ from bayes_opt.parameter import wrap_kernel
13
+
12
14
  if TYPE_CHECKING:
13
15
  from collections.abc import Callable
14
16
 
@@ -55,6 +57,7 @@ class ConstraintModel:
55
57
  fun: Callable[..., float] | Callable[..., NDArray[Float]] | None,
56
58
  lb: float | NDArray[Float],
57
59
  ub: float | NDArray[Float],
60
+ transform: Callable[[Any], Any] | None = None,
58
61
  random_state: int | RandomState | None = None,
59
62
  ) -> None:
60
63
  self.fun = fun
@@ -68,7 +71,7 @@ class ConstraintModel:
68
71
 
69
72
  self._model = [
70
73
  GaussianProcessRegressor(
71
- kernel=Matern(nu=2.5),
74
+ kernel=wrap_kernel(Matern(nu=2.5), transform) if transform is not None else Matern(nu=2.5),
72
75
  alpha=1e-6,
73
76
  normalize_y=True,
74
77
  n_restarts_optimizer=5,
@@ -14,6 +14,7 @@ from warnings import warn
14
14
 
15
15
  import numpy as np
16
16
 
17
+ from bayes_opt.parameter import FloatParameter
17
18
  from bayes_opt.target_space import TargetSpace
18
19
 
19
20
  if TYPE_CHECKING:
@@ -62,22 +63,19 @@ class SequentialDomainReductionTransformer(DomainTransformer):
62
63
 
63
64
  def __init__(
64
65
  self,
66
+ parameters: Iterable[str] | None = None,
65
67
  gamma_osc: float = 0.7,
66
68
  gamma_pan: float = 1.0,
67
69
  eta: float = 0.9,
68
70
  minimum_window: NDArray[Float] | Sequence[float] | Mapping[str, float] | float = 0.0,
69
71
  ) -> None:
72
+ # TODO: Ensure that this is only applied to continuous parameters
73
+ self.parameters = parameters
70
74
  self.gamma_osc = gamma_osc
71
75
  self.gamma_pan = gamma_pan
72
76
  self.eta = eta
73
77
 
74
- self.minimum_window_value: NDArray[Float] | Sequence[float] | float
75
- if isinstance(minimum_window, Mapping):
76
- self.minimum_window_value = [
77
- item[1] for item in sorted(minimum_window.items(), key=lambda x: x[0])
78
- ]
79
- else:
80
- self.minimum_window_value = minimum_window
78
+ self.minimum_window_value = minimum_window
81
79
 
82
80
  def initialize(self, target_space: TargetSpace) -> None:
83
81
  """Initialize all of the parameters.
@@ -87,6 +85,15 @@ class SequentialDomainReductionTransformer(DomainTransformer):
87
85
  target_space : TargetSpace
88
86
  TargetSpace this DomainTransformer operates on.
89
87
  """
88
+ if isinstance(self.minimum_window_value, Mapping):
89
+ self.minimum_window_value = [self.minimum_window_value[key] for key in target_space.keys]
90
+ else:
91
+ self.minimum_window_value = self.minimum_window_value
92
+
93
+ any_not_float = any([not isinstance(p, FloatParameter) for p in target_space._params_config.values()])
94
+ if any_not_float:
95
+ msg = "Domain reduction is only supported for all-FloatParameter optimization."
96
+ raise ValueError(msg)
90
97
  # Set the original bounds
91
98
  self.original_bounds = np.copy(target_space.bounds)
92
99
  self.bounds = [self.original_bounds]
@@ -127,19 +127,19 @@ class ScreenLogger(_Tracker):
127
127
  x_ = ("T" if x else "F") if self._default_cell_size < 5 else str(x)
128
128
  return f"{x_:<{self._default_cell_size}}"
129
129
 
130
- def _format_key(self, key: str) -> str:
131
- """Format a key.
130
+ def _format_str(self, str_: str) -> str:
131
+ """Format a str.
132
132
 
133
133
  Parameters
134
134
  ----------
135
- key : string
135
+ str_ : str
136
136
  Value to format.
137
137
 
138
138
  Returns
139
139
  -------
140
140
  A stringified, formatted version of `x`.
141
141
  """
142
- s = f"{key:^{self._default_cell_size}}"
142
+ s = f"{str_:^{self._default_cell_size}}"
143
143
  if len(s) > self._default_cell_size:
144
144
  return s[: self._default_cell_size - 3] + "..."
145
145
  return s
@@ -168,7 +168,10 @@ class ScreenLogger(_Tracker):
168
168
  if self._is_constrained:
169
169
  cells[2] = self._format_bool(res["allowed"])
170
170
  params = res.get("params", {})
171
- cells[3:] = [self._format_number(params.get(key, float("nan"))) for key in keys]
171
+ cells[3:] = [
172
+ instance.space._params_config[key].to_string(val, self._default_cell_size)
173
+ for key, val in params.items()
174
+ ]
172
175
 
173
176
  return "| " + " | ".join(colour + x + self._colour_reset for x in cells if x is not None) + " |"
174
177
 
@@ -188,10 +191,10 @@ class ScreenLogger(_Tracker):
188
191
  # iter, target, allowed [, *params]
189
192
  cells: list[str | None] = [None] * (3 + len(keys))
190
193
 
191
- cells[:2] = self._format_key("iter"), self._format_key("target")
194
+ cells[:2] = self._format_str("iter"), self._format_str("target")
192
195
  if self._is_constrained:
193
- cells[2] = self._format_key("allowed")
194
- cells[3:] = [self._format_key(key) for key in keys]
196
+ cells[2] = self._format_str("allowed")
197
+ cells[3:] = [self._format_str(key) for key in keys]
195
198
 
196
199
  line = "| " + " | ".join(x for x in cells if x is not None) + " |"
197
200
  self._header_length = len(line)