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,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
|
enn/turbo/tr_helpers.py
ADDED
|
@@ -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
|
|
|
@@ -13,17 +12,25 @@ class TurboGP(TurboGPBase):
|
|
|
13
12
|
outputscale_constraint,
|
|
14
13
|
ard_dims: int,
|
|
15
14
|
) -> None:
|
|
15
|
+
import torch
|
|
16
16
|
from gpytorch.kernels import MaternKernel, ScaleKernel
|
|
17
17
|
from gpytorch.means import ConstantMean
|
|
18
18
|
|
|
19
19
|
super().__init__(train_x, train_y, likelihood)
|
|
20
|
-
|
|
20
|
+
batch_shape = (
|
|
21
|
+
torch.Size(train_y.shape[:-1])
|
|
22
|
+
if getattr(train_y, "ndim", 0) > 1
|
|
23
|
+
else torch.Size()
|
|
24
|
+
)
|
|
25
|
+
self.mean_module = ConstantMean(batch_shape=batch_shape)
|
|
21
26
|
base_kernel = MaternKernel(
|
|
22
27
|
nu=2.5,
|
|
23
28
|
ard_num_dims=ard_dims,
|
|
29
|
+
batch_shape=batch_shape,
|
|
24
30
|
lengthscale_constraint=lengthscale_constraint,
|
|
25
31
|
)
|
|
26
32
|
self.covar_module = ScaleKernel(
|
|
27
33
|
base_kernel,
|
|
34
|
+
batch_shape=batch_shape,
|
|
28
35
|
outputscale_constraint=outputscale_constraint,
|
|
29
36
|
)
|
enn/turbo/turbo_gp_base.py
CHANGED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Any
|
|
3
|
+
from enn.enn.enn_util import standardize_y
|
|
4
|
+
from .types import GPDataPrep, GPFitResult
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _prepare_gp_data(
|
|
8
|
+
x_obs_list: list, y_obs_list: list, yvar_obs_list: list | None
|
|
9
|
+
) -> GPDataPrep:
|
|
10
|
+
import numpy as np
|
|
11
|
+
import torch
|
|
12
|
+
|
|
13
|
+
x = np.asarray(x_obs_list, dtype=float)
|
|
14
|
+
y = np.asarray(y_obs_list, dtype=float)
|
|
15
|
+
if y.ndim not in (1, 2):
|
|
16
|
+
raise ValueError(y.shape)
|
|
17
|
+
is_multi = y.ndim == 2 and y.shape[1] > 1
|
|
18
|
+
if yvar_obs_list is not None:
|
|
19
|
+
if len(yvar_obs_list) != len(y_obs_list):
|
|
20
|
+
raise ValueError(
|
|
21
|
+
f"yvar_obs_list length {len(yvar_obs_list)} != y_obs_list length {len(y_obs_list)}"
|
|
22
|
+
)
|
|
23
|
+
if is_multi:
|
|
24
|
+
raise ValueError("yvar_obs_list not supported for multi-output GP")
|
|
25
|
+
if is_multi:
|
|
26
|
+
y_mean, y_std = y.mean(axis=0), y.std(axis=0)
|
|
27
|
+
y_std = np.where(y_std < 1e-6, 1.0, y_std)
|
|
28
|
+
z = (y - y_mean) / y_std
|
|
29
|
+
train_y = torch.as_tensor(z.T, dtype=torch.float64)
|
|
30
|
+
else:
|
|
31
|
+
y_mean, y_std = standardize_y(y)
|
|
32
|
+
z = (y - y_mean) / y_std
|
|
33
|
+
train_y = torch.as_tensor(z, dtype=torch.float64)
|
|
34
|
+
return GPDataPrep(
|
|
35
|
+
train_x=torch.as_tensor(x, dtype=torch.float64),
|
|
36
|
+
train_y=train_y,
|
|
37
|
+
is_multi=is_multi,
|
|
38
|
+
y_mean=y_mean,
|
|
39
|
+
y_std=y_std,
|
|
40
|
+
y_raw=y,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _build_gp_model(
|
|
45
|
+
train_x: Any,
|
|
46
|
+
train_y: Any,
|
|
47
|
+
is_multi: bool,
|
|
48
|
+
num_dim: int,
|
|
49
|
+
*,
|
|
50
|
+
yvar_obs_list: list | None,
|
|
51
|
+
gp_y_std: Any,
|
|
52
|
+
y: Any,
|
|
53
|
+
) -> tuple[Any, Any]:
|
|
54
|
+
import numpy as np
|
|
55
|
+
import torch
|
|
56
|
+
from gpytorch.constraints import Interval
|
|
57
|
+
from gpytorch.likelihoods import GaussianLikelihood
|
|
58
|
+
from .turbo_gp import TurboGP
|
|
59
|
+
from .turbo_gp_noisy import TurboGPNoisy
|
|
60
|
+
|
|
61
|
+
ls_constr, os_constr = Interval(0.005, 2.0), Interval(0.05, 20.0)
|
|
62
|
+
if yvar_obs_list is not None:
|
|
63
|
+
y_var = np.asarray(yvar_obs_list, dtype=float)
|
|
64
|
+
train_y_var = torch.as_tensor(y_var / (gp_y_std**2), dtype=torch.float64)
|
|
65
|
+
model = TurboGPNoisy(
|
|
66
|
+
train_x=train_x,
|
|
67
|
+
train_y=train_y,
|
|
68
|
+
train_y_var=train_y_var,
|
|
69
|
+
lengthscale_constraint=ls_constr,
|
|
70
|
+
outputscale_constraint=os_constr,
|
|
71
|
+
ard_dims=num_dim,
|
|
72
|
+
).to(dtype=train_x.dtype)
|
|
73
|
+
return model, model.likelihood
|
|
74
|
+
noise_constr = Interval(5e-4, 0.2)
|
|
75
|
+
num_out = int(y.shape[1]) if is_multi else None
|
|
76
|
+
if is_multi:
|
|
77
|
+
likelihood = GaussianLikelihood(
|
|
78
|
+
noise_constraint=noise_constr, batch_shape=torch.Size([num_out])
|
|
79
|
+
).to(dtype=train_y.dtype)
|
|
80
|
+
else:
|
|
81
|
+
likelihood = GaussianLikelihood(noise_constraint=noise_constr).to(
|
|
82
|
+
dtype=train_y.dtype
|
|
83
|
+
)
|
|
84
|
+
model = TurboGP(
|
|
85
|
+
train_x=train_x,
|
|
86
|
+
train_y=train_y,
|
|
87
|
+
likelihood=likelihood,
|
|
88
|
+
lengthscale_constraint=ls_constr,
|
|
89
|
+
outputscale_constraint=os_constr,
|
|
90
|
+
ard_dims=num_dim,
|
|
91
|
+
).to(dtype=train_x.dtype)
|
|
92
|
+
likelihood.noise = (
|
|
93
|
+
torch.full((num_out,), 0.005, dtype=train_y.dtype)
|
|
94
|
+
if is_multi
|
|
95
|
+
else torch.tensor(0.005, dtype=train_y.dtype)
|
|
96
|
+
)
|
|
97
|
+
return model, likelihood
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _init_gp_hyperparams(
|
|
101
|
+
model: Any, is_multi: bool, num_dim: int, num_out: int | None, dtype: Any
|
|
102
|
+
) -> None:
|
|
103
|
+
import torch
|
|
104
|
+
|
|
105
|
+
if is_multi:
|
|
106
|
+
model.covar_module.outputscale = torch.ones(num_out, dtype=dtype)
|
|
107
|
+
model.covar_module.base_kernel.lengthscale = torch.full(
|
|
108
|
+
(num_out, 1, num_dim), 0.5, dtype=dtype
|
|
109
|
+
)
|
|
110
|
+
else:
|
|
111
|
+
model.covar_module.outputscale = torch.tensor(1.0, dtype=dtype)
|
|
112
|
+
model.covar_module.base_kernel.lengthscale = torch.full(
|
|
113
|
+
(num_dim,), 0.5, dtype=dtype
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _train_gp(
|
|
118
|
+
model: Any, likelihood: Any, train_x: Any, train_y: Any, num_steps: int
|
|
119
|
+
) -> None:
|
|
120
|
+
import torch
|
|
121
|
+
from gpytorch.mlls import ExactMarginalLogLikelihood
|
|
122
|
+
|
|
123
|
+
model.train()
|
|
124
|
+
likelihood.train()
|
|
125
|
+
mll = ExactMarginalLogLikelihood(likelihood, model)
|
|
126
|
+
optimizer = torch.optim.Adam(model.parameters(), lr=0.1)
|
|
127
|
+
for _ in range(num_steps):
|
|
128
|
+
optimizer.zero_grad()
|
|
129
|
+
loss = -mll(model(train_x), train_y)
|
|
130
|
+
(loss.sum() if loss.ndim != 0 else loss).backward()
|
|
131
|
+
optimizer.step()
|
|
132
|
+
model.eval()
|
|
133
|
+
likelihood.eval()
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def fit_gp(
|
|
137
|
+
x_obs_list: list[float] | list[list[float]],
|
|
138
|
+
y_obs_list: list[float] | list[list[float]],
|
|
139
|
+
num_dim: int,
|
|
140
|
+
*,
|
|
141
|
+
yvar_obs_list: list[float] | None = None,
|
|
142
|
+
num_steps: int = 50,
|
|
143
|
+
) -> GPFitResult:
|
|
144
|
+
import numpy as np
|
|
145
|
+
|
|
146
|
+
x = np.asarray(x_obs_list, dtype=float)
|
|
147
|
+
y = np.asarray(y_obs_list, dtype=float)
|
|
148
|
+
n, is_multi = x.shape[0], y.ndim == 2 and y.shape[1] > 1
|
|
149
|
+
if n == 0:
|
|
150
|
+
return (
|
|
151
|
+
GPFitResult(
|
|
152
|
+
model=None,
|
|
153
|
+
likelihood=None,
|
|
154
|
+
y_mean=np.zeros(y.shape[1]),
|
|
155
|
+
y_std=np.ones(y.shape[1]),
|
|
156
|
+
)
|
|
157
|
+
if is_multi
|
|
158
|
+
else GPFitResult(model=None, likelihood=None, y_mean=0.0, y_std=1.0)
|
|
159
|
+
)
|
|
160
|
+
if n == 1 and is_multi:
|
|
161
|
+
return GPFitResult(
|
|
162
|
+
model=None,
|
|
163
|
+
likelihood=None,
|
|
164
|
+
y_mean=y[0].copy(),
|
|
165
|
+
y_std=np.ones(int(y.shape[1]), dtype=float),
|
|
166
|
+
)
|
|
167
|
+
gp_data = _prepare_gp_data(x_obs_list, y_obs_list, yvar_obs_list)
|
|
168
|
+
model, likelihood = _build_gp_model(
|
|
169
|
+
gp_data.train_x,
|
|
170
|
+
gp_data.train_y,
|
|
171
|
+
gp_data.is_multi,
|
|
172
|
+
num_dim,
|
|
173
|
+
yvar_obs_list=yvar_obs_list,
|
|
174
|
+
gp_y_std=gp_data.y_std,
|
|
175
|
+
y=gp_data.y_raw,
|
|
176
|
+
)
|
|
177
|
+
_init_gp_hyperparams(
|
|
178
|
+
model,
|
|
179
|
+
gp_data.is_multi,
|
|
180
|
+
num_dim,
|
|
181
|
+
int(gp_data.y_raw.shape[1]) if gp_data.is_multi else None,
|
|
182
|
+
gp_data.train_x.dtype,
|
|
183
|
+
)
|
|
184
|
+
_train_gp(model, likelihood, gp_data.train_x, gp_data.train_y, num_steps)
|
|
185
|
+
return GPFitResult(
|
|
186
|
+
model=model, likelihood=likelihood, y_mean=gp_data.y_mean, y_std=gp_data.y_std
|
|
187
|
+
)
|
enn/turbo/turbo_gp_noisy.py
CHANGED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
from .types import ObsLists, TellInputs
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def sobol_seed_for_state(
|
|
10
|
+
seed_base: int, *, restart_generation: int, n_obs: int, num_arms: int
|
|
11
|
+
) -> int:
|
|
12
|
+
mask64 = (1 << 64) - 1
|
|
13
|
+
x = int(seed_base) & mask64
|
|
14
|
+
x ^= (int(restart_generation) + 1) * 0xD1342543DE82EF95 & mask64
|
|
15
|
+
x ^= (int(n_obs) + 1) * 0x9E3779B97F4A7C15 & mask64
|
|
16
|
+
x ^= (int(num_arms) + 1) * 0xBF58476D1CE4E5B9 & mask64
|
|
17
|
+
x = (x + 0x9E3779B97F4A7C15) & mask64
|
|
18
|
+
z = x
|
|
19
|
+
z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9 & mask64
|
|
20
|
+
z = (z ^ (z >> 27)) * 0x94D049BB133111EB & mask64
|
|
21
|
+
z = z ^ (z >> 31)
|
|
22
|
+
return int(z & 0xFFFFFFFF)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def reset_timing(opt: object) -> None:
|
|
26
|
+
setattr(opt, "_dt_fit", 0.0)
|
|
27
|
+
setattr(opt, "_dt_gen", 0.0)
|
|
28
|
+
setattr(opt, "_dt_sel", 0.0)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def validate_tell_inputs(
|
|
32
|
+
x: np.ndarray, y: np.ndarray, y_var: np.ndarray | None, num_dim: int
|
|
33
|
+
) -> TellInputs:
|
|
34
|
+
import numpy as np
|
|
35
|
+
|
|
36
|
+
x = np.asarray(x, dtype=float)
|
|
37
|
+
y = np.asarray(y, dtype=float)
|
|
38
|
+
if x.ndim != 2 or x.shape[1] != num_dim:
|
|
39
|
+
raise ValueError(x.shape)
|
|
40
|
+
if y.ndim == 2:
|
|
41
|
+
if y.shape[0] != x.shape[0]:
|
|
42
|
+
raise ValueError((x.shape, y.shape))
|
|
43
|
+
num_metrics = y.shape[1]
|
|
44
|
+
elif y.ndim == 1:
|
|
45
|
+
if y.shape[0] != x.shape[0]:
|
|
46
|
+
raise ValueError((x.shape, y.shape))
|
|
47
|
+
num_metrics = 1
|
|
48
|
+
else:
|
|
49
|
+
raise ValueError(y.shape)
|
|
50
|
+
if y_var is not None:
|
|
51
|
+
y_var = np.asarray(y_var, dtype=float)
|
|
52
|
+
if y_var.shape != y.shape:
|
|
53
|
+
raise ValueError((y.shape, y_var.shape))
|
|
54
|
+
return TellInputs(x=x, y=y, y_var=y_var, num_metrics=num_metrics)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def trim_trailing_observations(
|
|
58
|
+
x_obs_list: list,
|
|
59
|
+
y_obs_list: list,
|
|
60
|
+
y_tr_list: list,
|
|
61
|
+
yvar_obs_list: list,
|
|
62
|
+
*,
|
|
63
|
+
trailing_obs: int,
|
|
64
|
+
incumbent_indices: np.ndarray,
|
|
65
|
+
) -> ObsLists:
|
|
66
|
+
import numpy as np
|
|
67
|
+
|
|
68
|
+
num_total = len(x_obs_list)
|
|
69
|
+
if num_total <= trailing_obs:
|
|
70
|
+
return ObsLists(
|
|
71
|
+
x_obs=x_obs_list,
|
|
72
|
+
y_obs=y_obs_list,
|
|
73
|
+
y_tr=y_tr_list,
|
|
74
|
+
yvar_obs=yvar_obs_list,
|
|
75
|
+
)
|
|
76
|
+
start_idx = max(0, num_total - trailing_obs)
|
|
77
|
+
recent_indices = set(range(start_idx, num_total))
|
|
78
|
+
keep_indices = set(incumbent_indices.tolist()) | recent_indices
|
|
79
|
+
if len(keep_indices) > trailing_obs:
|
|
80
|
+
keep_indices = set(incumbent_indices.tolist())
|
|
81
|
+
remaining_slots = trailing_obs - len(keep_indices)
|
|
82
|
+
if remaining_slots > 0:
|
|
83
|
+
recent_non_incumbent = [
|
|
84
|
+
i for i in range(num_total - 1, -1, -1) if i not in keep_indices
|
|
85
|
+
][:remaining_slots]
|
|
86
|
+
keep_indices.update(recent_non_incumbent)
|
|
87
|
+
indices = np.array(sorted(keep_indices), dtype=int)
|
|
88
|
+
x_array = np.asarray(x_obs_list, dtype=float)
|
|
89
|
+
y_obs_array = np.asarray(y_obs_list, dtype=float)
|
|
90
|
+
y_tr_array = np.asarray(y_tr_list, dtype=float)
|
|
91
|
+
new_x = x_array[indices].tolist()
|
|
92
|
+
new_y_obs = y_obs_array[indices].tolist()
|
|
93
|
+
new_y_tr = y_tr_array[indices].tolist() if y_tr_array.size > 0 else []
|
|
94
|
+
new_yvar = yvar_obs_list
|
|
95
|
+
if len(yvar_obs_list) == len(y_obs_array):
|
|
96
|
+
yvar_array = np.asarray(yvar_obs_list, dtype=float)
|
|
97
|
+
new_yvar = yvar_array[indices].tolist()
|
|
98
|
+
return ObsLists(x_obs=new_x, y_obs=new_y_obs, y_tr=new_y_tr, yvar_obs=new_yvar)
|