bayesian-optimization 3.0.1__tar.gz → 3.2.0__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.
@@ -1,7 +1,9 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: bayesian-optimization
3
- Version: 3.0.1
3
+ Version: 3.2.0
4
4
  Summary: Bayesian Optimization package
5
+ Author: Fernando Nogueira
6
+ Author-email: Fernando Nogueira <fmfnogueira@gmail.com>
5
7
  License: The MIT License (MIT)
6
8
 
7
9
  Copyright (c) 2014 Fernando M. F. Nogueira
@@ -11,9 +13,6 @@ License: The MIT License (MIT)
11
13
  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
12
14
 
13
15
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
14
- Author: Fernando Nogueira
15
- Author-email: fmfnogueira@gmail.com
16
- Requires-Python: >=3.9,<4.0
17
16
  Classifier: License :: OSI Approved :: MIT License
18
17
  Classifier: Programming Language :: Python
19
18
  Classifier: Programming Language :: Python :: 3
@@ -22,12 +21,31 @@ Classifier: Programming Language :: Python :: 3.10
22
21
  Classifier: Programming Language :: Python :: 3.11
23
22
  Classifier: Programming Language :: Python :: 3.12
24
23
  Classifier: Programming Language :: Python :: 3.13
25
- Requires-Dist: colorama (>=0.4.6,<1.0.0)
26
- Requires-Dist: numpy (>=1.25) ; python_version < "3.13"
27
- Requires-Dist: numpy (>=2.1.3) ; python_version >= "3.13"
28
- Requires-Dist: scikit-learn (>=1.0.0,<2.0.0)
29
- Requires-Dist: scipy (>=1.0.0,<2.0.0) ; python_version < "3.13"
30
- Requires-Dist: scipy (>=1.14.1,<2.0.0) ; python_version >= "3.13"
24
+ Classifier: Programming Language :: Python :: 3.14
25
+ Requires-Dist: colorama>=0.4.6
26
+ Requires-Dist: numpy>=1.25 ; python_full_version < '3.13'
27
+ Requires-Dist: numpy>=2.1.3 ; python_full_version >= '3.13'
28
+ Requires-Dist: packaging>=20.0
29
+ Requires-Dist: scikit-learn>=1.0.0
30
+ Requires-Dist: scipy>=1.0.0 ; python_full_version < '3.13'
31
+ Requires-Dist: scipy>=1.14.1 ; python_full_version >= '3.13'
32
+ Requires-Dist: coverage>=7.4.1 ; extra == 'dev'
33
+ Requires-Dist: jupyter>=1.0.0 ; extra == 'dev'
34
+ Requires-Dist: matplotlib>=3.0 ; extra == 'dev'
35
+ Requires-Dist: nbconvert>=7.14.2 ; extra == 'dev'
36
+ Requires-Dist: nbformat>=5.9.2 ; extra == 'dev'
37
+ Requires-Dist: nbsphinx>=0.9.4 ; extra == 'dev'
38
+ Requires-Dist: pre-commit>=3.7.1 ; extra == 'dev'
39
+ Requires-Dist: pytest>=8.0.0 ; extra == 'dev'
40
+ Requires-Dist: pytest-cov>=4.1.0 ; extra == 'dev'
41
+ Requires-Dist: ruff>=0.12.3 ; extra == 'dev'
42
+ Requires-Dist: sphinx-immaterial>=0.12.0 ; extra == 'dev'
43
+ Requires-Dist: sphinx>=7.0.0 ; python_full_version < '3.10' and extra == 'dev'
44
+ Requires-Dist: sphinx>=8.0.0 ; python_full_version >= '3.10' and extra == 'dev'
45
+ Requires-Dist: sphinx-autodoc-typehints>=2.3.0 ; python_full_version < '3.10' and extra == 'dev'
46
+ Requires-Dist: sphinx-autodoc-typehints>=2.4.0 ; python_full_version >= '3.10' and extra == 'dev'
47
+ Requires-Python: >=3.9
48
+ Provides-Extra: dev
31
49
  Description-Content-Type: text/markdown
32
50
 
33
51
  <div align="center">
@@ -230,4 +248,3 @@ For optimization over non-float parameters:
230
248
  publisher={Elsevier}
231
249
  }
232
250
  ```
233
-
@@ -452,6 +452,14 @@ class UpperConfidenceBound(AcquisitionFunction):
452
452
  if kappa < 0:
453
453
  error_msg = "kappa must be greater than or equal to 0."
454
454
  raise ValueError(error_msg)
455
+ if exploration_decay is not None and not (0 < exploration_decay <= 1):
456
+ error_msg = "exploration_decay must be greater than 0 and less than or equal to 1."
457
+ raise ValueError(error_msg)
458
+ if exploration_decay_delay is not None and (
459
+ not isinstance(exploration_decay_delay, int) or exploration_decay_delay < 0
460
+ ):
461
+ error_msg = "exploration_decay_delay must be an integer greater than or equal to 0."
462
+ raise ValueError(error_msg)
455
463
 
456
464
  super().__init__(random_state=random_state)
457
465
  self.kappa = kappa
@@ -604,6 +612,18 @@ class ProbabilityOfImprovement(AcquisitionFunction):
604
612
  exploration_decay_delay: int | None = None,
605
613
  random_state: int | RandomState | None = None,
606
614
  ) -> None:
615
+ if xi < 0:
616
+ error_msg = "xi must be greater than or equal to 0."
617
+ raise ValueError(error_msg)
618
+ if exploration_decay is not None and not (0 < exploration_decay <= 1):
619
+ error_msg = "exploration_decay must be greater than 0 and less than or equal to 1."
620
+ raise ValueError(error_msg)
621
+ if exploration_decay_delay is not None and (
622
+ not isinstance(exploration_decay_delay, int) or exploration_decay_delay < 0
623
+ ):
624
+ error_msg = "exploration_decay_delay must be an integer greater than or equal to 0."
625
+ raise ValueError(error_msg)
626
+
607
627
  super().__init__(random_state=random_state)
608
628
  self.xi = xi
609
629
  self.exploration_decay = exploration_decay
@@ -766,6 +786,7 @@ class ExpectedImprovement(AcquisitionFunction):
766
786
  Decay rate for xi. If None, no decay is applied.
767
787
 
768
788
  exploration_decay_delay : int, default None
789
+ Delay for decay. If None, decay is applied from the start.
769
790
 
770
791
  random_state : int, RandomState, default None
771
792
  Set the random state for reproducibility.
@@ -778,6 +799,18 @@ class ExpectedImprovement(AcquisitionFunction):
778
799
  exploration_decay_delay: int | None = None,
779
800
  random_state: int | RandomState | None = None,
780
801
  ) -> None:
802
+ if xi < 0:
803
+ error_msg = "xi must be greater than or equal to 0."
804
+ raise ValueError(error_msg)
805
+ if exploration_decay is not None and not (0 < exploration_decay <= 1):
806
+ error_msg = "exploration_decay must be greater than 0 and less than or equal to 1."
807
+ raise ValueError(error_msg)
808
+ if exploration_decay_delay is not None and (
809
+ not isinstance(exploration_decay_delay, int) or exploration_decay_delay < 0
810
+ ):
811
+ error_msg = "exploration_decay_delay must be an integer greater than or equal to 0."
812
+ raise ValueError(error_msg)
813
+
781
814
  super().__init__(random_state=random_state)
782
815
  self.xi = xi
783
816
  self.exploration_decay = exploration_decay
@@ -8,6 +8,7 @@ from __future__ import annotations
8
8
 
9
9
  import json
10
10
  from collections import deque
11
+ from collections.abc import Iterable
11
12
  from os import PathLike
12
13
  from pathlib import Path
13
14
  from typing import TYPE_CHECKING, Any
@@ -55,6 +56,11 @@ class BayesianOptimization:
55
56
  Dictionary with parameters names as keys and a tuple with minimum
56
57
  and maximum values.
57
58
 
59
+ acquisition_function: AcquisitionFunction, optional(default=None)
60
+ The acquisition function to use for suggesting new points to evaluate.
61
+ If None, defaults to UpperConfidenceBound for unconstrained problems
62
+ and ExpectedImprovement for constrained problems.
63
+
58
64
  constraint: NonlinearConstraint.
59
65
  Note that the names of arguments of the constraint function and of
60
66
  f need to be the same.
@@ -170,6 +176,92 @@ class BayesianOptimization:
170
176
  """
171
177
  return self._space.res()
172
178
 
179
+ def predict(
180
+ self,
181
+ params: dict[str, Any] | Iterable[dict[str, Any]],
182
+ return_std=False,
183
+ return_cov=False,
184
+ fit_gp=True,
185
+ ) -> float | NDArray[Float] | tuple[float | NDArray[Float], float | NDArray[Float]]:
186
+ """Predict the target function value at given parameters.
187
+
188
+ Parameters
189
+ ---------
190
+ params: dict or iterable of dicts
191
+ The parameters where the prediction is made.
192
+
193
+ return_std: bool, optional(default=False)
194
+ If True, the standard deviation of the prediction is returned.
195
+
196
+ return_cov: bool, optional(default=False)
197
+ If True, the covariance of the prediction is returned.
198
+
199
+ fit_gp: bool, optional(default=True)
200
+ If True, the internal Gaussian Process model is fitted before
201
+ making the prediction.
202
+
203
+ Returns
204
+ -------
205
+ mean: float or np.ndarray
206
+ The predicted mean of the target function at the given parameters.
207
+ When params is a dict, returns a scalar. When params is an iterable,
208
+ returns a 1D array.
209
+
210
+ std_or_cov: float or np.ndarray (only if return_std or return_cov is True)
211
+ The predicted standard deviation or covariance of the target function
212
+ at the given parameters.
213
+ """
214
+ # Validate param types
215
+ if isinstance(params, dict):
216
+ params_array = self._space.params_to_array(params).reshape(1, -1)
217
+ single_param = True
218
+ elif isinstance(params, Iterable) and not isinstance(params, str):
219
+ # convert iterable of dicts to 2D array
220
+ params_array = np.array([self._space.params_to_array(p) for p in params])
221
+ single_param = False
222
+ else:
223
+ msg = f"params must be a dict or iterable of dicts, got {type(params).__name__}"
224
+ raise TypeError(msg)
225
+
226
+ # Validate mutual exclusivity of return_std and return_cov
227
+ if return_std and return_cov:
228
+ msg = "return_std and return_cov cannot both be True"
229
+ raise ValueError(msg)
230
+
231
+ if fit_gp:
232
+ if len(self._space) == 0:
233
+ msg = (
234
+ "The Gaussian Process model cannot be fitted with zero observations. To use predict(), "
235
+ "without fitting the GP, set fit_gp=False. The predictions will then be made using the "
236
+ "GP prior."
237
+ )
238
+ raise RuntimeError(msg)
239
+ self.acquisition_function._fit_gp(self._gp, self._space)
240
+
241
+ res = self._gp.predict(params_array, return_std=return_std, return_cov=return_cov)
242
+
243
+ if return_std or return_cov:
244
+ mean, std_or_cov = res
245
+ else:
246
+ mean = res
247
+
248
+ # Shape semantics: dict input returns scalars, list input returns arrays
249
+ # Ensure list input always returns arrays (convert scalar to 1D if needed)
250
+ if not single_param and mean.ndim == 0:
251
+ mean = np.atleast_1d(mean)
252
+ # ruff complains when nesting conditionals, so this three-way split is necessary
253
+ if not single_param and (return_std or return_cov) and std_or_cov.ndim == 0:
254
+ std_or_cov = np.atleast_1d(std_or_cov)
255
+
256
+ if single_param and mean.ndim > 0:
257
+ mean = mean[0]
258
+ if single_param and return_std and std_or_cov.ndim > 0:
259
+ std_or_cov = std_or_cov[0]
260
+
261
+ if return_std or return_cov:
262
+ return mean, std_or_cov
263
+ return mean
264
+
173
265
  def register(
174
266
  self, params: ParamsType, target: float, constraint_value: float | NDArray[Float] | None = None
175
267
  ) -> None:
@@ -298,8 +390,8 @@ class BayesianOptimization:
298
390
  probe based on the acquisition function. This means that the GP may
299
391
  not be fitted on all points registered to the target space when the
300
392
  method completes. If you intend to use the GP model after the
301
- optimization routine, make sure to fit it manually, e.g. by calling
302
- ``optimizer._gp.fit(optimizer.space.params, optimizer.space.target)``.
393
+ optimization routine, make sure to call predict() with fit_gp=True.
394
+
303
395
  """
304
396
  # Log optimization start
305
397
  self.logger.log_optimization_start(self._space.keys)
@@ -347,19 +439,7 @@ class BayesianOptimization:
347
439
  ----------
348
440
  path : str or PathLike
349
441
  Path to save the optimization state
350
-
351
- Raises
352
- ------
353
- ValueError
354
- If attempting to save state before collecting any samples.
355
442
  """
356
- if len(self._space) == 0:
357
- msg = (
358
- "Cannot save optimizer state before collecting any samples. "
359
- "Please probe or register at least one point before saving."
360
- )
361
- raise ValueError(msg)
362
-
363
443
  random_state = None
364
444
  if self._random_state is not None:
365
445
  state_tuple = self._random_state.get_state()
@@ -438,7 +518,8 @@ class BayesianOptimization:
438
518
  # Set the GP parameters
439
519
  self.set_gp_params(**gp_params)
440
520
 
441
- self._gp.fit(self._space.params, self._space.target)
521
+ if len(self._space):
522
+ self._gp.fit(self._space.params, self._space.target)
442
523
 
443
524
  if state["random_state"] is not None:
444
525
  random_state_tuple = (
@@ -240,7 +240,7 @@ class ConstraintModel:
240
240
  return self._model[0].predict(X).reshape(X_shape[:-1])
241
241
 
242
242
  result = np.column_stack([gp.predict(X) for gp in self._model])
243
- return result.reshape(X_shape[:-1] + (len(self._lb),))
243
+ return result.reshape(*X_shape[:-1], len(self._lb))
244
244
 
245
245
  def allowed(self, constraint_values: NDArray[Float]) -> NDArray[np.bool_]:
246
246
  """Check whether `constraint_values` fulfills the specified limits.
@@ -82,7 +82,7 @@ class ScreenLogger:
82
82
  result = ""
83
83
  width = self._default_cell_size
84
84
  # Keep negative sign, exponent, and as many decimal places as possible
85
- if "-" in s:
85
+ if x < 0:
86
86
  result += "-"
87
87
  width -= 1
88
88
  s = s[1:]
@@ -96,8 +96,6 @@ class ScreenLogger:
96
96
  width -= dot_pos
97
97
  if width > 0:
98
98
  result += s[dot_pos : dot_pos + width]
99
- else:
100
- result += s[:width]
101
99
  if "e" in s:
102
100
  result += end
103
101
  result = result.ljust(self._default_cell_size)
@@ -489,7 +489,10 @@ def wrap_kernel(kernel: kernels.Kernel, transform: Callable[[Any], Any]) -> kern
489
489
  def __reduce__(self) -> str | tuple[Any, ...]:
490
490
  return (wrap_kernel, (kernel, transform))
491
491
 
492
- return WrappedKernel(**kernel.get_params())
492
+ wrapped_instance = WrappedKernel.__new__(WrappedKernel)
493
+ wrapped_instance.__dict__.update(kernel.__dict__)
494
+ wrapped_instance._transform = transform
495
+ return wrapped_instance
493
496
 
494
497
 
495
498
  def _copy_signature(source_fct: Callable[..., Any]) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
@@ -0,0 +1,61 @@
1
+ [project]
2
+ name = "bayesian-optimization"
3
+ version = "3.2.0"
4
+ description = "Bayesian Optimization package"
5
+ authors = [{ name = "Fernando Nogueira", email = "fmfnogueira@gmail.com" }]
6
+ license = { file = "LICENSE" }
7
+ readme = "README.md"
8
+ requires-python = ">=3.9"
9
+ classifiers = [
10
+ "License :: OSI Approved :: MIT License",
11
+ "Programming Language :: Python",
12
+ "Programming Language :: Python :: 3",
13
+ "Programming Language :: Python :: 3.9",
14
+ "Programming Language :: Python :: 3.10",
15
+ "Programming Language :: Python :: 3.11",
16
+ "Programming Language :: Python :: 3.12",
17
+ "Programming Language :: Python :: 3.13",
18
+ "Programming Language :: Python :: 3.14",
19
+ ]
20
+ dependencies = [
21
+ "colorama>=0.4.6",
22
+ "numpy>=1.25; python_version<'3.13'",
23
+ "numpy>=2.1.3; python_version>='3.13'",
24
+ "packaging>=20.0",
25
+ "scikit-learn>=1.0.0",
26
+ "scipy>=1.0.0; python_version<'3.13'",
27
+ "scipy>=1.14.1; python_version>='3.13'",
28
+ ]
29
+
30
+ [project.optional-dependencies]
31
+ dev = [
32
+ "coverage>=7.4.1",
33
+ "jupyter>=1.0.0",
34
+ "matplotlib>=3.0",
35
+ "nbconvert>=7.14.2",
36
+ "nbformat>=5.9.2",
37
+ "nbsphinx>=0.9.4",
38
+ "pre-commit>=3.7.1",
39
+ "pytest>=8.0.0",
40
+ "pytest-cov>=4.1.0",
41
+ "ruff>=0.12.3",
42
+ "sphinx-immaterial>=0.12.0",
43
+ "sphinx>=7.0.0; python_version<'3.10'",
44
+ "sphinx>=8.0.0; python_version>='3.10'",
45
+ "sphinx-autodoc-typehints>=2.3.0; python_version<'3.10'",
46
+ "sphinx-autodoc-typehints>=2.4.0; python_version>='3.10'",
47
+ ]
48
+
49
+ [build-system]
50
+ requires = ["uv_build>=0.7.21,<0.8.0"]
51
+ build-backend = "uv_build"
52
+
53
+ [tool.uv.build-backend]
54
+ module-name = "bayes_opt"
55
+ module-root = ""
56
+
57
+ [tool.coverage.report]
58
+ exclude_lines = [
59
+ "pragma: no cover",
60
+ "if TYPE_CHECKING:",
61
+ ]
@@ -1,70 +0,0 @@
1
- [project]
2
- name = "bayesian-optimization"
3
- version = "3.0.1"
4
- description = "Bayesian Optimization package"
5
- authors = [{ name = "Fernando Nogueira", email = "fmfnogueira@gmail.com" }]
6
- license = { file = "LICENSE" }
7
- readme = "README.md"
8
- requires-python = ">=3.9,<4.0"
9
- classifiers = [
10
- "License :: OSI Approved :: MIT License",
11
- "Programming Language :: Python",
12
- "Programming Language :: Python :: 3",
13
- "Programming Language :: Python :: 3.9",
14
- "Programming Language :: Python :: 3.10",
15
- "Programming Language :: Python :: 3.11",
16
- "Programming Language :: Python :: 3.12",
17
- "Programming Language :: Python :: 3.13",
18
- ]
19
- dependencies = [
20
- "scikit-learn>=1.0.0,<2.0.0",
21
- "numpy>=1.25; python_version<'3.13'",
22
- "numpy>=2.1.3; python_version>='3.13'",
23
- "scipy>=1.0.0,<2.0.0; python_version<'3.13'",
24
- "scipy>=1.14.1,<2.0.0; python_version>='3.13'",
25
- "colorama>=0.4.6,<1.0.0",
26
- ]
27
-
28
- [tool.poetry]
29
- requires-poetry = ">=2.0"
30
- packages = [{ include = "bayes_opt" }]
31
-
32
-
33
- [tool.poetry.group.dev] # for testing/developing
34
- optional = true
35
- [tool.poetry.group.dev.dependencies]
36
- pytest = "^8.0.0"
37
- pytest-cov = "^4.1.0"
38
- coverage = "^7.4.1"
39
- ruff = "0.6.6"
40
- pre-commit = "^3.7.1"
41
-
42
-
43
- [tool.poetry.group.nbtools] # for running/converting notebooks
44
- optional = true
45
- [tool.poetry.group.nbtools.dependencies]
46
- nbformat = "^5.9.2"
47
- nbconvert = "^7.14.2"
48
- jupyter = "^1.0.0"
49
- matplotlib = "^3.0"
50
- nbsphinx = "^0.9.4"
51
- sphinx-immaterial = "^0.12.0"
52
- sphinx = [
53
- { version = "^7.0.0", python = "<3.10" },
54
- { version = "^8.0.0", python = ">=3.10" },
55
- ]
56
- sphinx-autodoc-typehints = [
57
- { version = "^2.3.0", python = "<3.10" },
58
- { version = "^2.4.0", python = ">=3.10" },
59
- ]
60
-
61
-
62
- [build-system]
63
- requires = ["poetry-core>=2.0"]
64
- build-backend = "poetry.core.masonry.api"
65
-
66
- [tool.coverage.report]
67
- exclude_lines = [
68
- "pragma: no cover",
69
- "if TYPE_CHECKING:",
70
- ]
@@ -15,10 +15,10 @@ __version__ = importlib.metadata.version("bayesian-optimization")
15
15
 
16
16
 
17
17
  __all__ = [
18
- "acquisition",
19
18
  "BayesianOptimization",
20
- "TargetSpace",
21
19
  "ConstraintModel",
22
20
  "ScreenLogger",
23
21
  "SequentialDomainReductionTransformer",
22
+ "TargetSpace",
23
+ "acquisition",
24
24
  ]
@@ -4,9 +4,9 @@ from __future__ import annotations
4
4
 
5
5
  __all__ = [
6
6
  "BayesianOptimizationError",
7
- "NotUniqueError",
8
7
  "ConstraintNotSupportedError",
9
8
  "NoValidPointRegisteredError",
9
+ "NotUniqueError",
10
10
  "TargetSpaceEmptyError",
11
11
  ]
12
12