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.
- enn/__init__.py +25 -13
- enn/benchmarks/__init__.py +3 -0
- enn/benchmarks/ackley.py +5 -0
- enn/benchmarks/ackley_class.py +17 -0
- enn/benchmarks/ackley_core.py +12 -0
- enn/benchmarks/double_ackley.py +24 -0
- enn/enn/candidates.py +14 -0
- enn/enn/conditional_posterior_draw_internals.py +15 -0
- enn/enn/draw_internals.py +15 -0
- enn/enn/enn.py +16 -229
- enn/enn/enn_class.py +423 -0
- enn/enn/enn_conditional.py +325 -0
- enn/enn/enn_fit.py +77 -76
- enn/enn/enn_hash.py +79 -0
- enn/enn/enn_index.py +92 -0
- enn/enn/enn_like_protocol.py +35 -0
- enn/enn/enn_normal.py +3 -3
- enn/enn/enn_params.py +3 -9
- enn/enn/enn_params_class.py +24 -0
- enn/enn/enn_util.py +79 -37
- enn/enn/neighbor_data.py +14 -0
- enn/enn/neighbors.py +14 -0
- enn/enn/posterior_flags.py +8 -0
- enn/enn/weighted_stats.py +14 -0
- enn/turbo/components/__init__.py +41 -0
- enn/turbo/components/acquisition.py +13 -0
- enn/turbo/components/acquisition_optimizer_protocol.py +19 -0
- enn/turbo/components/builder.py +22 -0
- enn/turbo/components/chebyshev_incumbent_selector.py +76 -0
- enn/turbo/components/enn_surrogate.py +115 -0
- enn/turbo/components/gp_surrogate.py +144 -0
- enn/turbo/components/hnr_acq_optimizer.py +83 -0
- enn/turbo/components/incumbent_selector.py +11 -0
- enn/turbo/components/incumbent_selector_protocol.py +16 -0
- enn/turbo/components/no_incumbent_selector.py +21 -0
- enn/turbo/components/no_surrogate.py +49 -0
- enn/turbo/components/pareto_acq_optimizer.py +49 -0
- enn/turbo/components/posterior_result.py +12 -0
- enn/turbo/components/protocols.py +13 -0
- enn/turbo/components/random_acq_optimizer.py +21 -0
- enn/turbo/components/scalar_incumbent_selector.py +39 -0
- enn/turbo/components/surrogate_protocol.py +32 -0
- enn/turbo/components/surrogate_result.py +12 -0
- enn/turbo/components/surrogates.py +5 -0
- enn/turbo/components/thompson_acq_optimizer.py +49 -0
- enn/turbo/components/trust_region_protocol.py +24 -0
- enn/turbo/components/ucb_acq_optimizer.py +49 -0
- enn/turbo/config/__init__.py +87 -0
- enn/turbo/config/acq_type.py +8 -0
- enn/turbo/config/acquisition.py +26 -0
- enn/turbo/config/base.py +4 -0
- enn/turbo/config/candidate_gen_config.py +49 -0
- enn/turbo/config/candidate_rv.py +7 -0
- enn/turbo/config/draw_acquisition_config.py +14 -0
- enn/turbo/config/enn_index_driver.py +6 -0
- enn/turbo/config/enn_surrogate_config.py +42 -0
- enn/turbo/config/enums.py +7 -0
- enn/turbo/config/factory.py +118 -0
- enn/turbo/config/gp_surrogate_config.py +14 -0
- enn/turbo/config/hnr_optimizer_config.py +7 -0
- enn/turbo/config/init_config.py +17 -0
- enn/turbo/config/init_strategies/__init__.py +9 -0
- enn/turbo/config/init_strategies/hybrid_init.py +23 -0
- enn/turbo/config/init_strategies/init_strategy.py +19 -0
- enn/turbo/config/init_strategies/lhd_only_init.py +24 -0
- enn/turbo/config/morbo_tr_config.py +82 -0
- enn/turbo/config/nds_optimizer_config.py +7 -0
- enn/turbo/config/no_surrogate_config.py +14 -0
- enn/turbo/config/no_tr_config.py +31 -0
- enn/turbo/config/optimizer_config.py +72 -0
- enn/turbo/config/pareto_acquisition_config.py +14 -0
- enn/turbo/config/raasp_driver.py +6 -0
- enn/turbo/config/raasp_optimizer_config.py +7 -0
- enn/turbo/config/random_acquisition_config.py +14 -0
- enn/turbo/config/rescalarize.py +7 -0
- enn/turbo/config/surrogate.py +12 -0
- enn/turbo/config/trust_region.py +34 -0
- enn/turbo/config/turbo_tr_config.py +71 -0
- enn/turbo/config/ucb_acquisition_config.py +14 -0
- enn/turbo/config/validation.py +45 -0
- enn/turbo/hypervolume.py +30 -0
- enn/turbo/impl_helpers.py +68 -0
- enn/turbo/morbo_trust_region.py +250 -0
- enn/turbo/no_trust_region.py +58 -0
- enn/turbo/optimizer.py +300 -0
- enn/turbo/optimizer_config.py +8 -0
- enn/turbo/proposal.py +46 -39
- enn/turbo/sampling.py +21 -0
- enn/turbo/strategies/__init__.py +9 -0
- enn/turbo/strategies/lhd_only_strategy.py +36 -0
- enn/turbo/strategies/optimization_strategy.py +19 -0
- enn/turbo/strategies/turbo_hybrid_strategy.py +124 -0
- enn/turbo/tr_helpers.py +202 -0
- enn/turbo/turbo_gp.py +9 -2
- enn/turbo/turbo_gp_base.py +0 -1
- enn/turbo/turbo_gp_fit.py +187 -0
- enn/turbo/turbo_gp_noisy.py +0 -1
- enn/turbo/turbo_optimizer_utils.py +98 -0
- enn/turbo/turbo_trust_region.py +129 -63
- enn/turbo/turbo_utils.py +144 -117
- enn/turbo/types/__init__.py +7 -0
- enn/turbo/types/appendable_array.py +85 -0
- enn/turbo/types/gp_data_prep.py +13 -0
- enn/turbo/types/gp_fit_result.py +11 -0
- enn/turbo/types/obs_lists.py +10 -0
- enn/turbo/types/prepare_ask_result.py +14 -0
- enn/turbo/types/tell_inputs.py +14 -0
- {ennbo-0.1.0.dist-info → ennbo-0.1.7.dist-info}/METADATA +22 -14
- ennbo-0.1.7.dist-info/RECORD +111 -0
- enn/enn/__init__.py +0 -4
- enn/turbo/__init__.py +0 -11
- enn/turbo/base_turbo_impl.py +0 -98
- enn/turbo/lhd_only_impl.py +0 -42
- enn/turbo/turbo_config.py +0 -28
- enn/turbo/turbo_enn_impl.py +0 -176
- enn/turbo/turbo_mode.py +0 -10
- enn/turbo/turbo_mode_impl.py +0 -67
- enn/turbo/turbo_one_impl.py +0 -163
- enn/turbo/turbo_optimizer.py +0 -337
- enn/turbo/turbo_zero_impl.py +0 -24
- ennbo-0.1.0.dist-info/RECORD +0 -27
- {ennbo-0.1.0.dist-info → ennbo-0.1.7.dist-info}/WHEEL +0 -0
- {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
|
+
)
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
93
|
-
y_obs_list: list
|
|
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
|
-
|
|
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
|
-
|
|
104
|
-
|
|
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
|
-
|
|
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]),
|
|
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,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: ...
|