ennbo 0.1.2__py3-none-any.whl → 0.1.7__py3-none-any.whl

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 (123) hide show
  1. enn/__init__.py +25 -13
  2. enn/benchmarks/__init__.py +3 -0
  3. enn/benchmarks/ackley.py +5 -0
  4. enn/benchmarks/ackley_class.py +17 -0
  5. enn/benchmarks/ackley_core.py +12 -0
  6. enn/benchmarks/double_ackley.py +24 -0
  7. enn/enn/candidates.py +14 -0
  8. enn/enn/conditional_posterior_draw_internals.py +15 -0
  9. enn/enn/draw_internals.py +15 -0
  10. enn/enn/enn.py +16 -269
  11. enn/enn/enn_class.py +423 -0
  12. enn/enn/enn_conditional.py +325 -0
  13. enn/enn/enn_fit.py +69 -70
  14. enn/enn/enn_hash.py +79 -0
  15. enn/enn/enn_index.py +92 -0
  16. enn/enn/enn_like_protocol.py +35 -0
  17. enn/enn/enn_normal.py +0 -1
  18. enn/enn/enn_params.py +3 -22
  19. enn/enn/enn_params_class.py +24 -0
  20. enn/enn/enn_util.py +60 -46
  21. enn/enn/neighbor_data.py +14 -0
  22. enn/enn/neighbors.py +14 -0
  23. enn/enn/posterior_flags.py +8 -0
  24. enn/enn/weighted_stats.py +14 -0
  25. enn/turbo/components/__init__.py +41 -0
  26. enn/turbo/components/acquisition.py +13 -0
  27. enn/turbo/components/acquisition_optimizer_protocol.py +19 -0
  28. enn/turbo/components/builder.py +22 -0
  29. enn/turbo/components/chebyshev_incumbent_selector.py +76 -0
  30. enn/turbo/components/enn_surrogate.py +115 -0
  31. enn/turbo/components/gp_surrogate.py +144 -0
  32. enn/turbo/components/hnr_acq_optimizer.py +83 -0
  33. enn/turbo/components/incumbent_selector.py +11 -0
  34. enn/turbo/components/incumbent_selector_protocol.py +16 -0
  35. enn/turbo/components/no_incumbent_selector.py +21 -0
  36. enn/turbo/components/no_surrogate.py +49 -0
  37. enn/turbo/components/pareto_acq_optimizer.py +49 -0
  38. enn/turbo/components/posterior_result.py +12 -0
  39. enn/turbo/components/protocols.py +13 -0
  40. enn/turbo/components/random_acq_optimizer.py +21 -0
  41. enn/turbo/components/scalar_incumbent_selector.py +39 -0
  42. enn/turbo/components/surrogate_protocol.py +32 -0
  43. enn/turbo/components/surrogate_result.py +12 -0
  44. enn/turbo/components/surrogates.py +5 -0
  45. enn/turbo/components/thompson_acq_optimizer.py +49 -0
  46. enn/turbo/components/trust_region_protocol.py +24 -0
  47. enn/turbo/components/ucb_acq_optimizer.py +49 -0
  48. enn/turbo/config/__init__.py +87 -0
  49. enn/turbo/config/acq_type.py +8 -0
  50. enn/turbo/config/acquisition.py +26 -0
  51. enn/turbo/config/base.py +4 -0
  52. enn/turbo/config/candidate_gen_config.py +49 -0
  53. enn/turbo/config/candidate_rv.py +7 -0
  54. enn/turbo/config/draw_acquisition_config.py +14 -0
  55. enn/turbo/config/enn_index_driver.py +6 -0
  56. enn/turbo/config/enn_surrogate_config.py +42 -0
  57. enn/turbo/config/enums.py +7 -0
  58. enn/turbo/config/factory.py +118 -0
  59. enn/turbo/config/gp_surrogate_config.py +14 -0
  60. enn/turbo/config/hnr_optimizer_config.py +7 -0
  61. enn/turbo/config/init_config.py +17 -0
  62. enn/turbo/config/init_strategies/__init__.py +9 -0
  63. enn/turbo/config/init_strategies/hybrid_init.py +23 -0
  64. enn/turbo/config/init_strategies/init_strategy.py +19 -0
  65. enn/turbo/config/init_strategies/lhd_only_init.py +24 -0
  66. enn/turbo/config/morbo_tr_config.py +82 -0
  67. enn/turbo/config/nds_optimizer_config.py +7 -0
  68. enn/turbo/config/no_surrogate_config.py +14 -0
  69. enn/turbo/config/no_tr_config.py +31 -0
  70. enn/turbo/config/optimizer_config.py +72 -0
  71. enn/turbo/config/pareto_acquisition_config.py +14 -0
  72. enn/turbo/config/raasp_driver.py +6 -0
  73. enn/turbo/config/raasp_optimizer_config.py +7 -0
  74. enn/turbo/config/random_acquisition_config.py +14 -0
  75. enn/turbo/config/rescalarize.py +7 -0
  76. enn/turbo/config/surrogate.py +12 -0
  77. enn/turbo/config/trust_region.py +34 -0
  78. enn/turbo/config/turbo_tr_config.py +71 -0
  79. enn/turbo/config/ucb_acquisition_config.py +14 -0
  80. enn/turbo/config/validation.py +45 -0
  81. enn/turbo/hypervolume.py +30 -0
  82. enn/turbo/impl_helpers.py +68 -0
  83. enn/turbo/morbo_trust_region.py +131 -70
  84. enn/turbo/no_trust_region.py +32 -39
  85. enn/turbo/optimizer.py +300 -0
  86. enn/turbo/optimizer_config.py +8 -0
  87. enn/turbo/proposal.py +36 -38
  88. enn/turbo/sampling.py +21 -0
  89. enn/turbo/strategies/__init__.py +9 -0
  90. enn/turbo/strategies/lhd_only_strategy.py +36 -0
  91. enn/turbo/strategies/optimization_strategy.py +19 -0
  92. enn/turbo/strategies/turbo_hybrid_strategy.py +124 -0
  93. enn/turbo/tr_helpers.py +202 -0
  94. enn/turbo/turbo_gp.py +0 -1
  95. enn/turbo/turbo_gp_base.py +0 -1
  96. enn/turbo/turbo_gp_fit.py +187 -0
  97. enn/turbo/turbo_gp_noisy.py +0 -1
  98. enn/turbo/turbo_optimizer_utils.py +98 -0
  99. enn/turbo/turbo_trust_region.py +126 -58
  100. enn/turbo/turbo_utils.py +98 -161
  101. enn/turbo/types/__init__.py +7 -0
  102. enn/turbo/types/appendable_array.py +85 -0
  103. enn/turbo/types/gp_data_prep.py +13 -0
  104. enn/turbo/types/gp_fit_result.py +11 -0
  105. enn/turbo/types/obs_lists.py +10 -0
  106. enn/turbo/types/prepare_ask_result.py +14 -0
  107. enn/turbo/types/tell_inputs.py +14 -0
  108. {ennbo-0.1.2.dist-info → ennbo-0.1.7.dist-info}/METADATA +18 -11
  109. ennbo-0.1.7.dist-info/RECORD +111 -0
  110. enn/enn/__init__.py +0 -4
  111. enn/turbo/__init__.py +0 -11
  112. enn/turbo/base_turbo_impl.py +0 -144
  113. enn/turbo/lhd_only_impl.py +0 -49
  114. enn/turbo/turbo_config.py +0 -72
  115. enn/turbo/turbo_enn_impl.py +0 -201
  116. enn/turbo/turbo_mode.py +0 -10
  117. enn/turbo/turbo_mode_impl.py +0 -76
  118. enn/turbo/turbo_one_impl.py +0 -302
  119. enn/turbo/turbo_optimizer.py +0 -525
  120. enn/turbo/turbo_zero_impl.py +0 -29
  121. ennbo-0.1.2.dist-info/RECORD +0 -29
  122. {ennbo-0.1.2.dist-info → ennbo-0.1.7.dist-info}/WHEEL +0 -0
  123. {ennbo-0.1.2.dist-info → ennbo-0.1.7.dist-info}/licenses/LICENSE +0 -0
enn/turbo/proposal.py CHANGED
@@ -1,43 +1,41 @@
1
1
  from __future__ import annotations
2
-
3
2
  from typing import TYPE_CHECKING, Any, Callable
4
3
 
5
4
  if TYPE_CHECKING:
6
5
  import numpy as np
7
6
  from numpy.random import Generator
8
-
9
- from enn.enn import EpistemicNearestNeighbors
7
+ from enn.enn.enn_class import EpistemicNearestNeighbors
10
8
  from enn.enn.enn_params import ENNParams
11
-
12
9
  from .turbo_gp import TurboGP
13
-
14
- from .turbo_utils import gp_thompson_sample
10
+ from .config.enums import ENNIndexDriver
15
11
 
16
12
 
17
13
  def mk_enn(
18
14
  x_obs_list: list[float] | list[list[float]],
19
15
  y_obs_list: list[float] | list[list[float]],
20
- *,
21
- yvar_obs_list: list[float] | None = None,
22
16
  k: int,
17
+ yvar_obs_list: list[float] | None = None,
18
+ *,
23
19
  num_fit_samples: int | None = None,
24
20
  num_fit_candidates: int | None = None,
25
21
  scale_x: bool = False,
22
+ index_driver: ENNIndexDriver | Any | None = None,
26
23
  rng: Generator | Any | None = None,
27
24
  params_warm_start: ENNParams | Any | None = None,
28
25
  ) -> tuple[EpistemicNearestNeighbors | None, ENNParams | None]:
29
26
  import numpy as np
30
-
31
- from enn.enn import EpistemicNearestNeighbors
27
+ from enn.enn.enn_class import EpistemicNearestNeighbors
32
28
  from enn.enn.enn_params import ENNParams
29
+ from .config.enums import ENNIndexDriver
30
+
31
+ if index_driver is None:
32
+ index_driver = ENNIndexDriver.FLAT
33
33
 
34
34
  if len(x_obs_list) == 0:
35
35
  return None, None
36
36
  y_obs_array = np.asarray(y_obs_list, dtype=float)
37
37
  if y_obs_array.size == 0:
38
38
  return None, None
39
-
40
- # Preserve multi-metric shape if present, otherwise reshape to (n, 1)
41
39
  if y_obs_array.ndim == 1:
42
40
  y = y_obs_array.reshape(-1, 1)
43
41
  else:
@@ -56,10 +54,10 @@ def mk_enn(
56
54
  y,
57
55
  yvar,
58
56
  scale_x=scale_x,
57
+ index_driver=index_driver,
59
58
  )
60
59
  if len(enn_model) == 0:
61
60
  return None, None
62
-
63
61
  fitted_params: ENNParams | None = None
64
62
  if num_fit_samples is not None and rng is not None:
65
63
  from enn.enn.enn_fit import enn_fit
@@ -75,8 +73,11 @@ def mk_enn(
75
73
  params_warm_start=params_warm_start,
76
74
  )
77
75
  else:
78
- fitted_params = ENNParams(k=k, epi_var_scale=1.0, ale_homoscedastic_scale=0.0)
79
-
76
+ fitted_params = ENNParams(
77
+ k_num_neighbors=k,
78
+ epistemic_variance_scale=1.0,
79
+ aleatoric_variance_scale=0.0,
80
+ )
80
81
  return enn_model, fitted_params
81
82
 
82
83
 
@@ -98,45 +99,42 @@ def select_uniform(
98
99
  def select_gp_thompson(
99
100
  x_cand: np.ndarray,
100
101
  num_arms: int,
101
- x_obs_list: list[float] | list[list[float]],
102
- y_obs_list: list[float] | list[list[float]],
102
+ x_obs_list: list,
103
+ y_obs_list: list,
103
104
  num_dim: int,
105
+ *,
104
106
  gp_num_steps: int,
105
107
  rng: Generator | Any,
106
- gp_y_mean: float,
107
- gp_y_std: float,
108
+ gp_y_stats: tuple[float, float],
108
109
  select_sobol_fn: Callable[[np.ndarray, int], np.ndarray],
109
110
  from_unit_fn: Callable[[np.ndarray], np.ndarray],
110
- *,
111
111
  model: TurboGP | None = None,
112
- new_gp_y_mean: float | None = None,
113
- new_gp_y_std: float | None = None,
114
- ) -> tuple[np.ndarray, float, float, TurboGP | None]:
115
- from .turbo_utils import fit_gp
112
+ ) -> tuple[np.ndarray, tuple[float, float], TurboGP | None]:
113
+ from .turbo_gp_fit import fit_gp
116
114
 
115
+ gp_y_mean, gp_y_std = gp_y_stats
117
116
  if len(x_obs_list) == 0:
118
- return select_sobol_fn(x_cand, num_arms), gp_y_mean, gp_y_std, None
117
+ return select_sobol_fn(x_cand, num_arms), (gp_y_mean, gp_y_std), None
118
+ fitted_mean, fitted_std = gp_y_mean, gp_y_std
119
119
  if model is None:
120
- model, _likelihood, new_gp_y_mean, new_gp_y_std = fit_gp(
120
+ gp_result = fit_gp(
121
121
  x_obs_list,
122
122
  y_obs_list,
123
123
  num_dim,
124
124
  num_steps=gp_num_steps,
125
125
  )
126
+ model, fitted_mean, fitted_std = (
127
+ gp_result.model,
128
+ gp_result.y_mean,
129
+ gp_result.y_std,
130
+ )
126
131
  if model is None:
127
- return select_sobol_fn(x_cand, num_arms), gp_y_mean, gp_y_std, None
128
- if new_gp_y_mean is None:
129
- new_gp_y_mean = gp_y_mean
130
- if new_gp_y_std is None:
131
- new_gp_y_std = gp_y_std
132
+ return select_sobol_fn(x_cand, num_arms), (gp_y_mean, gp_y_std), None
132
133
  if x_cand.shape[0] < num_arms:
133
134
  raise ValueError((x_cand.shape[0], num_arms))
135
+ from .turbo_utils import gp_thompson_sample
136
+
134
137
  idx = gp_thompson_sample(
135
- model,
136
- x_cand,
137
- num_arms,
138
- rng,
139
- new_gp_y_mean,
140
- new_gp_y_std,
138
+ model, x_cand, num_arms, rng, gp_y_mean=fitted_mean, gp_y_std=fitted_std
141
139
  )
142
- return from_unit_fn(x_cand[idx]), new_gp_y_mean, new_gp_y_std, model
140
+ return from_unit_fn(x_cand[idx]), (fitted_mean, fitted_std), model
enn/turbo/sampling.py ADDED
@@ -0,0 +1,21 @@
1
+ from __future__ import annotations
2
+ from typing import TYPE_CHECKING
3
+ import numpy as np
4
+ from . import turbo_utils
5
+
6
+ if TYPE_CHECKING:
7
+ from numpy.random import Generator
8
+
9
+
10
+ def draw_lhd(*, bounds: np.ndarray, num_arms: int, rng: Generator) -> np.ndarray:
11
+ bounds = np.asarray(bounds, dtype=float)
12
+ if bounds.ndim != 2 or bounds.shape[1] != 2:
13
+ raise ValueError(f"bounds must be (d, 2), got {bounds.shape}")
14
+ num_arms = int(num_arms)
15
+ if num_arms <= 0:
16
+ raise ValueError(num_arms)
17
+ num_dim = int(bounds.shape[0])
18
+ return turbo_utils.from_unit(
19
+ turbo_utils.latin_hypercube(num_arms, num_dim, rng=rng),
20
+ bounds,
21
+ )
@@ -0,0 +1,9 @@
1
+ from .lhd_only_strategy import LHDOnlyStrategy
2
+ from .optimization_strategy import OptimizationStrategy
3
+ from .turbo_hybrid_strategy import TurboHybridStrategy
4
+
5
+ __all__ = [
6
+ "LHDOnlyStrategy",
7
+ "OptimizationStrategy",
8
+ "TurboHybridStrategy",
9
+ ]
@@ -0,0 +1,36 @@
1
+ from __future__ import annotations
2
+ from dataclasses import dataclass
3
+ from typing import TYPE_CHECKING
4
+ import numpy as np
5
+ from ..sampling import draw_lhd
6
+ from .optimization_strategy import OptimizationStrategy
7
+
8
+ if TYPE_CHECKING:
9
+ from ..optimizer import Optimizer
10
+ from ..types import TellInputs
11
+
12
+
13
+ @dataclass
14
+ class LHDOnlyStrategy(OptimizationStrategy):
15
+ _bounds: np.ndarray
16
+ _rng: object
17
+
18
+ @classmethod
19
+ def create(cls, *, bounds: np.ndarray, rng: object) -> LHDOnlyStrategy:
20
+ bounds = np.asarray(bounds, dtype=float)
21
+ if bounds.ndim != 2 or bounds.shape[1] != 2:
22
+ raise ValueError(f"bounds must be (d, 2), got {bounds.shape}")
23
+ return cls(_bounds=bounds, _rng=rng)
24
+
25
+ def ask(self, opt: Optimizer, num_arms: int) -> np.ndarray:
26
+ return draw_lhd(bounds=self._bounds, num_arms=num_arms, rng=opt._rng)
27
+
28
+ def init_progress(self) -> tuple[int, int] | None:
29
+ return None
30
+
31
+ def tell(
32
+ self, opt: Optimizer, inputs: TellInputs, *, x_unit: np.ndarray
33
+ ) -> np.ndarray:
34
+ del x_unit
35
+ opt._y_tr_list = inputs.y.tolist()
36
+ return np.asarray(inputs.y, dtype=float)
@@ -0,0 +1,19 @@
1
+ from __future__ import annotations
2
+ from abc import ABC, abstractmethod
3
+ from typing import TYPE_CHECKING
4
+ import numpy as np
5
+
6
+ if TYPE_CHECKING:
7
+ from ..optimizer import Optimizer
8
+ from ..types import TellInputs
9
+
10
+
11
+ class OptimizationStrategy(ABC):
12
+ @abstractmethod
13
+ def ask(self, opt: Optimizer, num_arms: int) -> np.ndarray: ...
14
+ @abstractmethod
15
+ def tell(
16
+ self, opt: Optimizer, inputs: TellInputs, *, x_unit: np.ndarray
17
+ ) -> np.ndarray: ...
18
+ @abstractmethod
19
+ def init_progress(self) -> tuple[int, int] | None: ...
@@ -0,0 +1,124 @@
1
+ from __future__ import annotations
2
+ import time
3
+ from dataclasses import dataclass
4
+ from typing import TYPE_CHECKING
5
+ import numpy as np
6
+ from ..sampling import draw_lhd
7
+ from ..types.appendable_array import AppendableArray
8
+ from .optimization_strategy import OptimizationStrategy
9
+
10
+ if TYPE_CHECKING:
11
+ from ..optimizer import Optimizer
12
+ from ..types import TellInputs
13
+
14
+
15
+ @dataclass
16
+ class TurboHybridStrategy(OptimizationStrategy):
17
+ _bounds: np.ndarray
18
+ _num_dim: int
19
+ _rng: object
20
+ _num_init: int
21
+ _init_lhd: np.ndarray
22
+ _init_idx: int = 0
23
+
24
+ @classmethod
25
+ def create(
26
+ cls, *, bounds: np.ndarray, rng: object, num_init: int | None
27
+ ) -> TurboHybridStrategy:
28
+ from numpy.random import Generator
29
+
30
+ bounds = np.asarray(bounds, dtype=float)
31
+ if bounds.ndim != 2 or bounds.shape[1] != 2:
32
+ raise ValueError(f"bounds must be (d, 2), got {bounds.shape}")
33
+ num_dim = int(bounds.shape[0])
34
+ if not isinstance(rng, Generator):
35
+ raise TypeError("rng must be a numpy.random.Generator")
36
+ n_init = int(num_init if num_init is not None else 2 * num_dim)
37
+ if n_init <= 0:
38
+ raise ValueError(f"num_init must be > 0, got {n_init}")
39
+ init_lhd = draw_lhd(bounds=bounds, num_arms=n_init, rng=rng)
40
+ return cls(
41
+ _bounds=bounds,
42
+ _num_dim=num_dim,
43
+ _rng=rng,
44
+ _num_init=n_init,
45
+ _init_lhd=init_lhd,
46
+ )
47
+
48
+ def _reset_init(self) -> None:
49
+ from numpy.random import Generator
50
+
51
+ if not isinstance(self._rng, Generator):
52
+ raise TypeError("rng must be a numpy.random.Generator")
53
+ self._init_lhd = draw_lhd(
54
+ bounds=self._bounds, num_arms=self._num_init, rng=self._rng
55
+ )
56
+ self._init_idx = 0
57
+
58
+ def _get_init_points(self, num_arms: int, *, fallback_fn=None) -> np.ndarray:
59
+ num_arms = int(num_arms)
60
+ num_to_return = min(num_arms, self._num_init - self._init_idx)
61
+ result = self._init_lhd[self._init_idx : self._init_idx + num_to_return]
62
+ self._init_idx += num_to_return
63
+ if num_to_return < num_arms:
64
+ extra = (
65
+ fallback_fn
66
+ or (lambda n: draw_lhd(bounds=self._bounds, num_arms=n, rng=self._rng))
67
+ )(num_arms - num_to_return)
68
+ result = np.vstack([result, extra])
69
+ return result
70
+
71
+ def ask(self, opt: Optimizer, num_arms: int) -> np.ndarray:
72
+ if opt._tr_state.needs_restart():
73
+ opt._tr_state.restart(opt._rng)
74
+ opt._restart_generation += 1
75
+ opt._x_obs = AppendableArray()
76
+ opt._y_obs = AppendableArray()
77
+ opt._yvar_obs = AppendableArray()
78
+ opt._y_tr_list = []
79
+ opt._incumbent_idx = None
80
+ opt._incumbent_x_unit = None
81
+ opt._incumbent_y_scalar = None
82
+ self._reset_init()
83
+ return self._get_init_points(num_arms)
84
+ if self._init_idx < self._num_init:
85
+
86
+ def fallback(n: int) -> np.ndarray:
87
+ return opt._ask_normal(n, is_fallback=True)
88
+
89
+ return self._get_init_points(
90
+ num_arms,
91
+ fallback_fn=fallback if len(opt._x_obs) > 0 else None,
92
+ )
93
+ if len(opt._x_obs) == 0:
94
+ return draw_lhd(bounds=self._bounds, num_arms=num_arms, rng=self._rng)
95
+ opt._tr_state.validate_request(int(num_arms))
96
+ return opt._ask_normal(int(num_arms))
97
+
98
+ def init_progress(self) -> tuple[int, int] | None:
99
+ return (int(self._init_idx), int(self._num_init))
100
+
101
+ def tell(
102
+ self, opt: Optimizer, inputs: TellInputs, *, x_unit: np.ndarray
103
+ ) -> np.ndarray:
104
+ x_all = opt._x_obs.view()
105
+ y_all = opt._y_obs.view()
106
+ y_var_all = opt._yvar_obs.view() if len(opt._yvar_obs) > 0 else None
107
+ t0 = time.perf_counter()
108
+ opt._surrogate.fit(
109
+ x_all, y_all, y_var_all, num_steps=opt._gp_num_steps, rng=opt._rng
110
+ )
111
+ opt._dt_fit = time.perf_counter() - t0
112
+ opt._y_tr_list = y_all.tolist()
113
+ opt._update_incumbent()
114
+ try:
115
+ new_posterior = opt._surrogate.predict(x_unit)
116
+ y_estimate = np.asarray(new_posterior.mu, dtype=float)
117
+ except RuntimeError:
118
+ y_estimate = np.asarray(inputs.y, dtype=float)
119
+ y_incumbent = opt._incumbent_y_scalar
120
+ y_obs = y_all
121
+ opt._tr_state.update(y_obs, y_incumbent)
122
+ if opt._trailing_obs is not None:
123
+ opt._trim_trailing_obs()
124
+ return y_estimate
@@ -0,0 +1,202 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ from .config.enums import CandidateRV, RAASPDriver
6
+
7
+ if TYPE_CHECKING:
8
+ import numpy as np
9
+ from numpy.random import Generator
10
+ from scipy.stats._qmc import QMCEngine
11
+
12
+
13
+ def compute_full_box_bounds_1d(
14
+ x_center: np.ndarray,
15
+ ) -> tuple[np.ndarray, np.ndarray]:
16
+ import numpy as np
17
+
18
+ lb = np.zeros_like(x_center, dtype=float)
19
+ ub = np.ones_like(x_center, dtype=float)
20
+ return lb, ub
21
+
22
+
23
+ def get_single_incumbent_index(
24
+ selector,
25
+ y: np.ndarray,
26
+ rng: Generator,
27
+ mu: np.ndarray | None = None,
28
+ ) -> np.ndarray:
29
+ import numpy as np
30
+
31
+ y = np.asarray(y, dtype=float)
32
+ if y.size == 0:
33
+ return np.array([], dtype=int)
34
+ best_idx = selector.select(y, mu, rng)
35
+ return np.array([best_idx])
36
+
37
+
38
+ def get_incumbent_index(
39
+ selector,
40
+ y: np.ndarray,
41
+ rng: Generator,
42
+ mu: np.ndarray | None = None,
43
+ ) -> int:
44
+ import numpy as np
45
+
46
+ y = np.asarray(y, dtype=float)
47
+ if y.size == 0:
48
+ raise ValueError("y is empty")
49
+ return int(selector.select(y, mu, rng))
50
+
51
+
52
+ def get_scalar_incumbent_value(
53
+ selector,
54
+ y_obs: np.ndarray,
55
+ rng: Generator,
56
+ *,
57
+ mu_obs: np.ndarray | None = None,
58
+ ) -> np.ndarray:
59
+ import numpy as np
60
+
61
+ y = np.asarray(y_obs, dtype=float)
62
+ if y.size == 0:
63
+ return np.array([], dtype=float)
64
+ idx = get_incumbent_index(selector, y, rng, mu=mu_obs)
65
+ use_mu = bool(getattr(selector, "noise_aware", False))
66
+ values = mu_obs if use_mu else y
67
+ if values is None:
68
+ raise ValueError("noise_aware incumbent selection requires mu_obs")
69
+ v = np.asarray(values, dtype=float)
70
+ if v.ndim == 2:
71
+ value = float(v[idx, 0])
72
+ elif v.ndim == 1:
73
+ value = float(v[idx])
74
+ else:
75
+ raise ValueError(v.shape)
76
+ return np.array([value], dtype=float)
77
+
78
+
79
+ class ScalarIncumbentMixin:
80
+ incumbent_selector: Any
81
+
82
+ def get_incumbent_index(
83
+ self,
84
+ y: np.ndarray | Any,
85
+ rng: Generator,
86
+ mu: np.ndarray | None = None,
87
+ ) -> int:
88
+ return get_incumbent_index(self.incumbent_selector, y, rng, mu=mu)
89
+
90
+ def get_incumbent_value(
91
+ self,
92
+ y_obs: np.ndarray | Any,
93
+ rng: Generator,
94
+ mu_obs: np.ndarray | None = None,
95
+ ) -> np.ndarray:
96
+ return get_scalar_incumbent_value(
97
+ self.incumbent_selector, y_obs, rng, mu_obs=mu_obs
98
+ )
99
+
100
+
101
+ def generate_tr_candidates_orig(
102
+ compute_bounds_1d: Any,
103
+ x_center: np.ndarray,
104
+ lengthscales: np.ndarray | None,
105
+ num_candidates: int,
106
+ *,
107
+ rng: Generator,
108
+ candidate_rv: CandidateRV = CandidateRV.SOBOL,
109
+ sobol_engine: QMCEngine | None = None,
110
+ ) -> np.ndarray:
111
+ from .turbo_utils import (
112
+ generate_raasp_candidates,
113
+ generate_raasp_candidates_uniform,
114
+ )
115
+
116
+ lb, ub = compute_bounds_1d(x_center, lengthscales)
117
+ if candidate_rv == CandidateRV.SOBOL:
118
+ if sobol_engine is None:
119
+ raise ValueError(
120
+ "sobol_engine is required when candidate_rv=CandidateRV.SOBOL"
121
+ )
122
+ return generate_raasp_candidates(
123
+ x_center, lb, ub, num_candidates, rng=rng, sobol_engine=sobol_engine
124
+ )
125
+ if candidate_rv == CandidateRV.UNIFORM:
126
+ return generate_raasp_candidates_uniform(
127
+ x_center, lb, ub, num_candidates, rng=rng
128
+ )
129
+ raise ValueError(candidate_rv)
130
+
131
+
132
+ def generate_tr_candidates_fast(
133
+ compute_bounds_1d: Any,
134
+ x_center: np.ndarray,
135
+ lengthscales: np.ndarray | None,
136
+ num_candidates: int,
137
+ *,
138
+ rng: Generator,
139
+ candidate_rv: CandidateRV,
140
+ num_pert: int,
141
+ ) -> np.ndarray:
142
+ import numpy as np
143
+ from scipy.stats import qmc
144
+
145
+ lb, ub = compute_bounds_1d(x_center, lengthscales)
146
+ num_dim = x_center.shape[-1]
147
+
148
+ candidates = np.empty((num_candidates, num_dim), dtype=float)
149
+ candidates[:] = x_center
150
+
151
+ prob_perturb = min(num_pert / num_dim, 1.0)
152
+ ks = rng.binomial(num_dim, prob_perturb, size=num_candidates)
153
+ ks = np.maximum(ks, 1)
154
+ max_k = int(np.max(ks))
155
+
156
+ if candidate_rv == CandidateRV.SOBOL:
157
+ sobol = qmc.Sobol(d=max_k, scramble=True, seed=int(rng.integers(0, 2**31)))
158
+ samples = sobol.random(num_candidates)
159
+ elif candidate_rv == CandidateRV.UNIFORM:
160
+ samples = rng.random((num_candidates, max_k))
161
+ else:
162
+ raise ValueError(candidate_rv)
163
+
164
+ for i in range(num_candidates):
165
+ k = ks[i]
166
+ idx = rng.choice(num_dim, size=k, replace=False)
167
+ candidates[i, idx] = lb[idx] + (ub[idx] - lb[idx]) * samples[i, :k]
168
+
169
+ return candidates
170
+
171
+
172
+ def generate_tr_candidates(
173
+ compute_bounds_1d: Any,
174
+ x_center: np.ndarray,
175
+ lengthscales: np.ndarray | None,
176
+ num_candidates: int,
177
+ *,
178
+ rng: Generator,
179
+ candidate_rv: CandidateRV,
180
+ sobol_engine: QMCEngine | None,
181
+ raasp_driver: RAASPDriver,
182
+ num_pert: int,
183
+ ) -> np.ndarray:
184
+ if raasp_driver == RAASPDriver.FAST:
185
+ return generate_tr_candidates_fast(
186
+ compute_bounds_1d,
187
+ x_center,
188
+ lengthscales,
189
+ num_candidates,
190
+ rng=rng,
191
+ candidate_rv=candidate_rv,
192
+ num_pert=num_pert,
193
+ )
194
+ return generate_tr_candidates_orig(
195
+ compute_bounds_1d,
196
+ x_center,
197
+ lengthscales,
198
+ num_candidates,
199
+ rng=rng,
200
+ candidate_rv=candidate_rv,
201
+ sobol_engine=sobol_engine,
202
+ )
enn/turbo/turbo_gp.py CHANGED
@@ -1,5 +1,4 @@
1
1
  from __future__ import annotations
2
-
3
2
  from .turbo_gp_base import TurboGPBase
4
3
 
5
4
 
@@ -1,5 +1,4 @@
1
1
  from __future__ import annotations
2
-
3
2
  from typing import TYPE_CHECKING, Any
4
3
 
5
4
  if TYPE_CHECKING: