ennbo 0.1.0__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 -229
  11. enn/enn/enn_class.py +423 -0
  12. enn/enn/enn_conditional.py +325 -0
  13. enn/enn/enn_fit.py +77 -76
  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 +3 -3
  18. enn/enn/enn_params.py +3 -9
  19. enn/enn/enn_params_class.py +24 -0
  20. enn/enn/enn_util.py +79 -37
  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 +250 -0
  84. enn/turbo/no_trust_region.py +58 -0
  85. enn/turbo/optimizer.py +300 -0
  86. enn/turbo/optimizer_config.py +8 -0
  87. enn/turbo/proposal.py +46 -39
  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 +9 -2
  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 +129 -63
  100. enn/turbo/turbo_utils.py +144 -117
  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.0.dist-info → ennbo-0.1.7.dist-info}/METADATA +22 -14
  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 -98
  113. enn/turbo/lhd_only_impl.py +0 -42
  114. enn/turbo/turbo_config.py +0 -28
  115. enn/turbo/turbo_enn_impl.py +0 -176
  116. enn/turbo/turbo_mode.py +0 -10
  117. enn/turbo/turbo_mode_impl.py +0 -67
  118. enn/turbo/turbo_one_impl.py +0 -163
  119. enn/turbo/turbo_optimizer.py +0 -337
  120. enn/turbo/turbo_zero_impl.py +0 -24
  121. ennbo-0.1.0.dist-info/RECORD +0 -27
  122. {ennbo-0.1.0.dist-info → ennbo-0.1.7.dist-info}/WHEEL +0 -0
  123. {ennbo-0.1.0.dist-info → ennbo-0.1.7.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,58 @@
1
+ from __future__ import annotations
2
+ from dataclasses import dataclass, field
3
+ from typing import TYPE_CHECKING, Any
4
+ from .tr_helpers import ScalarIncumbentMixin
5
+
6
+ if TYPE_CHECKING:
7
+ import numpy as np
8
+ from .components.incumbent_selector import IncumbentSelector
9
+ from .config.no_tr_config import NoTRConfig
10
+
11
+
12
+ @dataclass
13
+ class NoTrustRegion(ScalarIncumbentMixin):
14
+ config: NoTRConfig
15
+ num_dim: int
16
+ incumbent_selector: IncumbentSelector = field(default=None, repr=False)
17
+ length: float = field(default=1.0, init=False)
18
+
19
+ def __post_init__(self) -> None:
20
+ from .components.incumbent_selector import ScalarIncumbentSelector
21
+
22
+ if self.incumbent_selector is None:
23
+ self.incumbent_selector = ScalarIncumbentSelector(noise_aware=False)
24
+
25
+ @property
26
+ def num_metrics(self) -> int:
27
+ return 1
28
+
29
+ def update(self, y_obs: np.ndarray | Any, y_incumbent: np.ndarray | Any) -> None:
30
+ return
31
+
32
+ def needs_restart(self) -> bool:
33
+ return False
34
+
35
+ def restart(self, rng=None) -> None:
36
+ return
37
+
38
+ def validate_request(self, num_arms: int, *, is_fallback: bool = False) -> None:
39
+ pass
40
+
41
+ def compute_bounds_1d(
42
+ self,
43
+ x_center: np.ndarray | Any,
44
+ lengthscales: np.ndarray | None = None,
45
+ ) -> tuple[np.ndarray, np.ndarray]:
46
+ from .tr_helpers import compute_full_box_bounds_1d
47
+
48
+ return compute_full_box_bounds_1d(x_center)
49
+
50
+ def get_incumbent_indices(
51
+ self,
52
+ y: np.ndarray | Any,
53
+ rng,
54
+ mu: np.ndarray | None = None,
55
+ ) -> np.ndarray:
56
+ import numpy as np
57
+
58
+ return np.array([self.get_incumbent_index(y, rng, mu=mu)])
enn/turbo/optimizer.py ADDED
@@ -0,0 +1,300 @@
1
+ from __future__ import annotations
2
+
3
+ import time
4
+ from typing import TYPE_CHECKING
5
+
6
+ import numpy as np
7
+
8
+ from . import turbo_optimizer_utils, turbo_utils
9
+ from .components import AcquisitionOptimizer, Surrogate
10
+ from .config.enums import CandidateRV
11
+ from .strategies import OptimizationStrategy
12
+ from .types.appendable_array import AppendableArray
13
+
14
+ if TYPE_CHECKING:
15
+ from numpy.random import Generator
16
+
17
+ from .config.optimizer_config import OptimizerConfig
18
+
19
+
20
+ class Optimizer:
21
+ def __init__(
22
+ self,
23
+ *,
24
+ bounds: np.ndarray,
25
+ config: OptimizerConfig,
26
+ rng: Generator,
27
+ surrogate: Surrogate,
28
+ acquisition_optimizer: AcquisitionOptimizer,
29
+ strategy: OptimizationStrategy | None = None,
30
+ ) -> None:
31
+ self._config = config
32
+ bounds = np.asarray(bounds, dtype=float)
33
+ if bounds.ndim != 2 or bounds.shape[1] != 2:
34
+ raise ValueError(f"bounds must be (d, 2), got {bounds.shape}")
35
+ self._bounds = bounds
36
+ self._num_dim = bounds.shape[0]
37
+ self._rng = rng
38
+ self._surrogate = surrogate
39
+ self._acq_optimizer = acquisition_optimizer
40
+ self._strategy = (
41
+ strategy
42
+ if strategy is not None
43
+ else config.init.init_strategy.create_runtime_strategy(
44
+ bounds=self._bounds, rng=self._rng, num_init=config.init.num_init
45
+ )
46
+ )
47
+ self._tr_state = config.trust_region.build(
48
+ num_dim=self._num_dim,
49
+ rng=rng,
50
+ candidate_rv=config.candidate_rv,
51
+ )
52
+ self._trailing_obs = (
53
+ None if config.trailing_obs is None else int(config.trailing_obs)
54
+ )
55
+ self._gp_num_steps = 50
56
+ if self._trailing_obs is not None and self._trailing_obs <= 0:
57
+ raise ValueError(f"trailing_obs must be > 0, got {self._trailing_obs}")
58
+ self._x_obs = AppendableArray()
59
+ self._y_obs = AppendableArray()
60
+ self._yvar_obs = AppendableArray()
61
+ self._y_tr_list: list[float] | list[list[float]] = []
62
+ self._expects_yvar: bool | None = None
63
+ self._dt_fit = 0.0
64
+ self._dt_gen = 0.0
65
+ self._dt_sel = 0.0
66
+ self._dt_tell = 0.0
67
+ self._sobol_seed_base = int(rng.integers(2**31 - 1))
68
+ self._restart_generation = 0
69
+ self._incumbent_idx: int | None = None
70
+ self._incumbent_x_unit: np.ndarray | None = None
71
+ self._incumbent_y_scalar: np.ndarray | None = None
72
+
73
+ @property
74
+ def tr_obs_count(self) -> int:
75
+ return len(self._y_obs)
76
+
77
+ @property
78
+ def tr_length(self) -> float:
79
+ return float(self._tr_state.length)
80
+
81
+ def telemetry(self) -> turbo_utils.Telemetry:
82
+ return turbo_utils.Telemetry(
83
+ dt_fit=self._dt_fit,
84
+ dt_gen=self._dt_gen,
85
+ dt_sel=self._dt_sel,
86
+ dt_tell=self._dt_tell,
87
+ )
88
+
89
+ @property
90
+ def init_progress(self) -> tuple[int, int] | None:
91
+ return self._strategy.init_progress()
92
+
93
+ def ask(self, num_arms: int) -> np.ndarray:
94
+ num_arms = int(num_arms)
95
+ if num_arms <= 0:
96
+ raise ValueError(num_arms)
97
+ turbo_optimizer_utils.reset_timing(self)
98
+ return self._strategy.ask(self, num_arms)
99
+
100
+ def _ask_normal(self, num_arms: int, *, is_fallback: bool = False) -> np.ndarray:
101
+ self._tr_state.validate_request(num_arms, is_fallback=is_fallback)
102
+ self._maybe_resample_weights()
103
+ x_center = self._incumbent_x_unit
104
+ if x_center is None:
105
+ if len(self._y_obs) == 0:
106
+ raise RuntimeError("no observations")
107
+ x_center = np.full(self._num_dim, 0.5)
108
+ t0 = time.perf_counter()
109
+ lengthscales = self._surrogate.lengthscales
110
+ x_cand = self._generate_candidates(x_center, lengthscales, num_arms=num_arms)
111
+ self._dt_gen = time.perf_counter() - t0
112
+ t0 = time.perf_counter()
113
+ selected = self._acq_optimizer.select(
114
+ x_cand,
115
+ num_arms,
116
+ self._surrogate,
117
+ self._rng,
118
+ tr_state=self._tr_state,
119
+ )
120
+ self._dt_sel = time.perf_counter() - t0
121
+ return turbo_utils.from_unit(selected, self._bounds)
122
+
123
+ def _find_x_center(self, x_obs: np.ndarray, y_obs: np.ndarray) -> np.ndarray | None:
124
+ return self._incumbent_x_unit
125
+
126
+ def _maybe_resample_weights(self) -> None:
127
+ from .config.rescalarize import Rescalarize
128
+
129
+ if hasattr(self._tr_state, "rescalarize"):
130
+ if self._tr_state.rescalarize == Rescalarize.ON_PROPOSE:
131
+ self._tr_state.resample_weights(self._rng)
132
+
133
+ def _generate_candidates(
134
+ self,
135
+ x_center: np.ndarray,
136
+ lengthscales: np.ndarray | None,
137
+ *,
138
+ num_arms: int,
139
+ ) -> np.ndarray:
140
+ from . import tr_helpers
141
+
142
+ if lengthscales is not None:
143
+ lengthscales = np.asarray(lengthscales, dtype=float).reshape(-1)
144
+ if not np.all(np.isfinite(lengthscales)):
145
+ raise ValueError("lengthscales must be finite")
146
+ num_candidates = int(
147
+ self._config.candidates.num_candidates(
148
+ num_dim=self._num_dim, num_arms=num_arms
149
+ )
150
+ )
151
+ if num_candidates <= 0:
152
+ raise ValueError(num_candidates)
153
+ candidate_rv = self._config.candidate_rv
154
+ if candidate_rv == CandidateRV.SOBOL:
155
+ from scipy.stats import qmc
156
+
157
+ sobol_seed = turbo_optimizer_utils.sobol_seed_for_state(
158
+ self._sobol_seed_base,
159
+ restart_generation=self._restart_generation,
160
+ n_obs=len(self._x_obs),
161
+ num_arms=num_arms,
162
+ )
163
+ sobol_engine = qmc.Sobol(d=self._num_dim, scramble=True, seed=sobol_seed)
164
+ else:
165
+ sobol_engine = None
166
+ if getattr(self._tr_state, "uses_custom_candidate_gen", False):
167
+ return self._tr_state.generate_candidates(
168
+ x_center,
169
+ lengthscales,
170
+ num_candidates,
171
+ rng=self._rng,
172
+ sobol_engine=sobol_engine,
173
+ raasp_driver=self._config.raasp_driver,
174
+ num_pert=20,
175
+ )
176
+ return tr_helpers.generate_tr_candidates(
177
+ self._tr_state.compute_bounds_1d,
178
+ x_center,
179
+ lengthscales,
180
+ num_candidates,
181
+ rng=self._rng,
182
+ candidate_rv=candidate_rv,
183
+ sobol_engine=sobol_engine,
184
+ raasp_driver=self._config.raasp_driver,
185
+ num_pert=20,
186
+ )
187
+
188
+ def _validate_tell_inputs(
189
+ self, x: np.ndarray, y: np.ndarray, y_var: np.ndarray | None
190
+ ) -> turbo_optimizer_utils.TellInputs:
191
+ inputs = turbo_optimizer_utils.validate_tell_inputs(x, y, y_var, self._num_dim)
192
+ tr_num_metrics = getattr(self._tr_state, "num_metrics", 1)
193
+ if inputs.num_metrics != tr_num_metrics:
194
+ raise ValueError(
195
+ f"y has {inputs.num_metrics} metrics but trust region expects {tr_num_metrics}"
196
+ )
197
+ if self._expects_yvar is None:
198
+ self._expects_yvar = inputs.y_var is not None
199
+ if (inputs.y_var is not None) != bool(self._expects_yvar):
200
+ raise ValueError(
201
+ f"y_var must be {'provided' if self._expects_yvar else 'omitted'} on every tell()"
202
+ )
203
+ return inputs
204
+
205
+ def _update_incumbent(self) -> None:
206
+ if len(self._y_obs) == 0:
207
+ self._incumbent_idx = None
208
+ self._incumbent_x_unit = None
209
+ self._incumbent_y_scalar = None
210
+ return
211
+ x_obs = self._x_obs.view()
212
+ y_obs = self._y_obs.view()
213
+ candidate_indices = self._surrogate.get_incumbent_candidate_indices(y_obs)
214
+ x_cand = x_obs[candidate_indices]
215
+ y_cand = y_obs[candidate_indices]
216
+ mu_cand = None
217
+ noise_aware = False
218
+ if hasattr(self._tr_state, "incumbent_selector"):
219
+ noise_aware = getattr(
220
+ self._tr_state.incumbent_selector, "noise_aware", False
221
+ )
222
+ elif hasattr(self._tr_state, "config"):
223
+ noise_aware = getattr(self._tr_state.config, "noise_aware", False)
224
+ if noise_aware:
225
+ try:
226
+ mu_cand = self._surrogate.predict(x_cand).mu
227
+ except RuntimeError:
228
+ mu_cand = None
229
+ idx_in_cand = self._tr_state.get_incumbent_index(y_cand, self._rng, mu=mu_cand)
230
+ self._incumbent_idx = int(candidate_indices[idx_in_cand])
231
+ self._incumbent_x_unit = x_obs[self._incumbent_idx]
232
+ if noise_aware and mu_cand is not None:
233
+ self._incumbent_y_scalar = mu_cand[idx_in_cand : idx_in_cand + 1].copy()
234
+ else:
235
+ self._incumbent_y_scalar = y_cand[idx_in_cand : idx_in_cand + 1].copy()
236
+
237
+ def _trim_trailing_obs(self) -> None:
238
+ incumbent_indices = np.array([self._incumbent_idx], dtype=int)
239
+ obs = turbo_optimizer_utils.trim_trailing_observations(
240
+ self._x_obs.view().tolist(),
241
+ self._y_obs.view().tolist(),
242
+ self._y_tr_list,
243
+ self._yvar_obs.view().tolist() if len(self._yvar_obs) > 0 else [],
244
+ trailing_obs=self._trailing_obs,
245
+ incumbent_indices=incumbent_indices,
246
+ )
247
+ self._x_obs = AppendableArray()
248
+ for x in obs.x_obs:
249
+ self._x_obs.append(np.array(x))
250
+ self._y_obs = AppendableArray()
251
+ for y in obs.y_obs:
252
+ self._y_obs.append(np.array(y))
253
+ self._yvar_obs = AppendableArray()
254
+ if obs.yvar_obs:
255
+ for yvar in obs.yvar_obs:
256
+ self._yvar_obs.append(np.array(yvar))
257
+ self._y_tr_list = obs.y_tr
258
+
259
+ def _update_best_value_if_needed(self) -> None:
260
+ pass
261
+
262
+ def tell(
263
+ self, x: np.ndarray, y: np.ndarray, y_var: np.ndarray | None = None
264
+ ) -> np.ndarray:
265
+ with turbo_utils.record_duration(
266
+ lambda dt: setattr(self, "_dt_tell", float(dt))
267
+ ):
268
+ inputs = self._validate_tell_inputs(x, y, y_var)
269
+ if inputs.x.shape[0] == 0:
270
+ return (
271
+ np.array([], dtype=float)
272
+ if inputs.num_metrics == 1
273
+ else np.empty((0, inputs.num_metrics), dtype=float)
274
+ )
275
+ x_unit = turbo_utils.to_unit(inputs.x, self._bounds)
276
+ for i in range(inputs.x.shape[0]):
277
+ self._x_obs.append(x_unit[i])
278
+ self._y_obs.append(inputs.y[i])
279
+ if inputs.y_var is not None:
280
+ self._yvar_obs.append(inputs.y_var[i])
281
+ return self._strategy.tell(self, inputs, x_unit=x_unit)
282
+
283
+
284
+ def create_optimizer(
285
+ *,
286
+ bounds: np.ndarray,
287
+ config: OptimizerConfig,
288
+ rng: Generator,
289
+ ) -> Optimizer:
290
+ from .components.builder import build_acquisition_optimizer, build_surrogate
291
+
292
+ surrogate = build_surrogate(config)
293
+ acq_optimizer = build_acquisition_optimizer(config)
294
+ return Optimizer(
295
+ bounds=bounds,
296
+ config=config,
297
+ rng=rng,
298
+ surrogate=surrogate,
299
+ acquisition_optimizer=acq_optimizer,
300
+ )
@@ -0,0 +1,8 @@
1
+ from __future__ import annotations
2
+ from . import config as _config
3
+
4
+ __all__ = list(_config.__all__)
5
+
6
+
7
+ def __getattr__(name: str) -> object:
8
+ return getattr(_config, name)
enn/turbo/proposal.py CHANGED
@@ -1,45 +1,51 @@
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,
21
+ scale_x: bool = False,
22
+ index_driver: ENNIndexDriver | Any | None = None,
25
23
  rng: Generator | Any | None = None,
26
24
  params_warm_start: ENNParams | Any | None = None,
27
25
  ) -> tuple[EpistemicNearestNeighbors | None, ENNParams | None]:
28
26
  import numpy as np
29
-
30
- from enn.enn import EpistemicNearestNeighbors
27
+ from enn.enn.enn_class import EpistemicNearestNeighbors
31
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
32
33
 
33
34
  if len(x_obs_list) == 0:
34
35
  return None, None
35
36
  y_obs_array = np.asarray(y_obs_list, dtype=float)
36
37
  if y_obs_array.size == 0:
37
38
  return None, None
38
-
39
- y = y_obs_array.reshape(-1, 1)
39
+ if y_obs_array.ndim == 1:
40
+ y = y_obs_array.reshape(-1, 1)
41
+ else:
42
+ y = y_obs_array
40
43
  if yvar_obs_list is not None and len(yvar_obs_list) > 0:
41
44
  yvar_array = np.asarray(yvar_obs_list, dtype=float)
42
- yvar = yvar_array.reshape(-1, 1)
45
+ if yvar_array.ndim == 1:
46
+ yvar = yvar_array.reshape(-1, 1)
47
+ else:
48
+ yvar = yvar_array
43
49
  else:
44
50
  yvar = None
45
51
  x_obs_array = np.asarray(x_obs_list, dtype=float)
@@ -47,10 +53,11 @@ def mk_enn(
47
53
  x_obs_array,
48
54
  y,
49
55
  yvar,
56
+ scale_x=scale_x,
57
+ index_driver=index_driver,
50
58
  )
51
59
  if len(enn_model) == 0:
52
60
  return None, None
53
-
54
61
  fitted_params: ENNParams | None = None
55
62
  if num_fit_samples is not None and rng is not None:
56
63
  from enn.enn.enn_fit import enn_fit
@@ -66,8 +73,11 @@ def mk_enn(
66
73
  params_warm_start=params_warm_start,
67
74
  )
68
75
  else:
69
- fitted_params = ENNParams(k=k, epi_var_scale=1.0, ale_homoscedastic_scale=0.0)
70
-
76
+ fitted_params = ENNParams(
77
+ k_num_neighbors=k,
78
+ epistemic_variance_scale=1.0,
79
+ aleatoric_variance_scale=0.0,
80
+ )
71
81
  return enn_model, fitted_params
72
82
 
73
83
 
@@ -89,45 +99,42 @@ def select_uniform(
89
99
  def select_gp_thompson(
90
100
  x_cand: np.ndarray,
91
101
  num_arms: int,
92
- x_obs_list: list[float] | list[list[float]],
93
- y_obs_list: list[float] | list[list[float]],
102
+ x_obs_list: list,
103
+ y_obs_list: list,
94
104
  num_dim: int,
105
+ *,
95
106
  gp_num_steps: int,
96
107
  rng: Generator | Any,
97
- gp_y_mean: float,
98
- gp_y_std: float,
108
+ gp_y_stats: tuple[float, float],
99
109
  select_sobol_fn: Callable[[np.ndarray, int], np.ndarray],
100
110
  from_unit_fn: Callable[[np.ndarray], np.ndarray],
101
- *,
102
111
  model: TurboGP | None = None,
103
- new_gp_y_mean: float | None = None,
104
- new_gp_y_std: float | None = None,
105
- ) -> tuple[np.ndarray, float, float, TurboGP | None]:
106
- from .turbo_utils import fit_gp
112
+ ) -> tuple[np.ndarray, tuple[float, float], TurboGP | None]:
113
+ from .turbo_gp_fit import fit_gp
107
114
 
115
+ gp_y_mean, gp_y_std = gp_y_stats
108
116
  if len(x_obs_list) == 0:
109
- 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
110
119
  if model is None:
111
- model, _likelihood, new_gp_y_mean, new_gp_y_std = fit_gp(
120
+ gp_result = fit_gp(
112
121
  x_obs_list,
113
122
  y_obs_list,
114
123
  num_dim,
115
124
  num_steps=gp_num_steps,
116
125
  )
126
+ model, fitted_mean, fitted_std = (
127
+ gp_result.model,
128
+ gp_result.y_mean,
129
+ gp_result.y_std,
130
+ )
117
131
  if model is None:
118
- return select_sobol_fn(x_cand, num_arms), gp_y_mean, gp_y_std, None
119
- if new_gp_y_mean is None:
120
- new_gp_y_mean = gp_y_mean
121
- if new_gp_y_std is None:
122
- new_gp_y_std = gp_y_std
132
+ return select_sobol_fn(x_cand, num_arms), (gp_y_mean, gp_y_std), None
123
133
  if x_cand.shape[0] < num_arms:
124
134
  raise ValueError((x_cand.shape[0], num_arms))
135
+ from .turbo_utils import gp_thompson_sample
136
+
125
137
  idx = gp_thompson_sample(
126
- model,
127
- x_cand,
128
- num_arms,
129
- rng,
130
- new_gp_y_mean,
131
- new_gp_y_std,
138
+ model, x_cand, num_arms, rng, gp_y_mean=fitted_mean, gp_y_std=fitted_std
132
139
  )
133
- 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: ...