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.
- 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 -269
- enn/enn/enn_class.py +423 -0
- enn/enn/enn_conditional.py +325 -0
- enn/enn/enn_fit.py +69 -70
- 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 +0 -1
- enn/enn/enn_params.py +3 -22
- enn/enn/enn_params_class.py +24 -0
- enn/enn/enn_util.py +60 -46
- 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 +131 -70
- enn/turbo/no_trust_region.py +32 -39
- enn/turbo/optimizer.py +300 -0
- enn/turbo/optimizer_config.py +8 -0
- enn/turbo/proposal.py +36 -38
- 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 +0 -1
- 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 +126 -58
- enn/turbo/turbo_utils.py +98 -161
- 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.2.dist-info → ennbo-0.1.7.dist-info}/METADATA +18 -11
- 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 -144
- enn/turbo/lhd_only_impl.py +0 -49
- enn/turbo/turbo_config.py +0 -72
- enn/turbo/turbo_enn_impl.py +0 -201
- enn/turbo/turbo_mode.py +0 -10
- enn/turbo/turbo_mode_impl.py +0 -76
- enn/turbo/turbo_one_impl.py +0 -302
- enn/turbo/turbo_optimizer.py +0 -525
- enn/turbo/turbo_zero_impl.py +0 -29
- ennbo-0.1.2.dist-info/RECORD +0 -29
- {ennbo-0.1.2.dist-info → ennbo-0.1.7.dist-info}/WHEEL +0 -0
- {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(
|
|
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
|
|
102
|
-
y_obs_list: list
|
|
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
|
-
|
|
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
|
-
|
|
113
|
-
|
|
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
|
-
|
|
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]),
|
|
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: ...
|
|
@@ -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