Skip to content

Commit 844927b

Browse files
perezed00till-m
andauthored
bounds_transformer could bypass global_bounds due to the test logic within _trim function in domain_reduction.py (#441)
* Update trim bounds in domain_reduction.py Previously, when the new upper limit was less than the original lower limit, the new_bounds could bypass the global_bounds. * Update test_seq_domain_red.py Added test cases to catch an error when both bounds of new_bounds exceeded the global_bounds * Update domain_reduction.py _trim function now avoids an error when both bounds for a given parameter in new_bounds exceed the global_bounds * Update domain_reduction.py comments * fixed English in domain_reduction.py * use numpy to sort bounds, boundary exceeded warn. * simple sort test added * domain_red windows target_space to global_bounds Added windowing function to improve the convergence of optimizers that use domain_reduction. Improved comments and documentation. * target_space.max respects bounds; SDRT warnings * Remove unused function. This function was used to prototype a solution. It should not have been pushed and can be removed. * Updated target_space.py docstrings * Update tests/test_target_space.py Co-authored-by: till-m <[email protected]> * Added pbound warnings, updated various tests. * updated line spacing for consistency and style * added pbound test condition --------- Co-authored-by: till-m <[email protected]>
1 parent 8cf4531 commit 844927b

File tree

6 files changed

+285
-76
lines changed

6 files changed

+285
-76
lines changed

bayes_opt/domain_reduction.py

+88-31
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import numpy as np
44
from .target_space import TargetSpace
5+
from warnings import warn
56

67

78
class DomainTransformer():
@@ -36,7 +37,10 @@ def __init__(
3637
self.minimum_window_value = minimum_window
3738

3839
def initialize(self, target_space: TargetSpace) -> None:
39-
"""Initialize all of the parameters"""
40+
"""Initialize all of the parameters.
41+
"""
42+
43+
# Set the original bounds
4044
self.original_bounds = np.copy(target_space.bounds)
4145
self.bounds = [self.original_bounds]
4246

@@ -47,6 +51,7 @@ def initialize(self, target_space: TargetSpace) -> None:
4751
else:
4852
self.minimum_window = [self.minimum_window_value] * len(target_space.bounds)
4953

54+
# Set initial values
5055
self.previous_optimal = np.mean(target_space.bounds, axis=1)
5156
self.current_optimal = np.mean(target_space.bounds, axis=1)
5257
self.r = target_space.bounds[:, 1] - target_space.bounds[:, 0]
@@ -72,14 +77,13 @@ def initialize(self, target_space: TargetSpace) -> None:
7277
self._window_bounds_compatibility(self.original_bounds)
7378

7479
def _update(self, target_space: TargetSpace) -> None:
75-
80+
""" Updates contraction rate, window size, and window center.
81+
"""
7682
# setting the previous
7783
self.previous_optimal = self.current_optimal
7884
self.previous_d = self.current_d
79-
80-
self.current_optimal = target_space.params[
81-
np.argmax(target_space.target)
82-
]
85+
86+
self.current_optimal = target_space.params_to_array(target_space.max()['params'])
8387

8488
self.current_d = 2.0 * (self.current_optimal -
8589
self.previous_optimal) / self.r
@@ -97,32 +101,84 @@ def _update(self, target_space: TargetSpace) -> None:
97101
self.r = self.contraction_rate * self.r
98102

99103
def _trim(self, new_bounds: np.array, global_bounds: np.array) -> np.array:
100-
for i, variable in enumerate(new_bounds):
101-
if variable[0] < global_bounds[i, 0]:
102-
variable[0] = global_bounds[i, 0]
103-
if variable[1] > global_bounds[i, 1]:
104-
variable[1] = global_bounds[i, 1]
105-
for i, entry in enumerate(new_bounds):
106-
if entry[0] > entry[1]:
107-
new_bounds[i, 0] = entry[1]
108-
new_bounds[i, 1] = entry[0]
109-
window_width = abs(entry[0] - entry[1])
110-
if window_width < self.minimum_window[i]:
111-
dw = (self.minimum_window[i] - window_width) / 2.0
112-
left_expansion_space = abs(global_bounds[i, 0] - entry[0]) # should be non-positive
113-
right_expansion_space = abs(global_bounds[i, 1] - entry[1]) # should be non-negative
114-
# conservative
115-
dw_l = min(dw, left_expansion_space)
116-
dw_r = min(dw, right_expansion_space)
117-
# this crawls towards the edge
118-
ddw_r = dw_r + max(dw - dw_l, 0)
119-
ddw_l = dw_l + max(dw - dw_r, 0)
120-
new_bounds[i, 0] -= ddw_l
121-
new_bounds[i, 1] += ddw_r
104+
"""
105+
Adjust the new_bounds and verify that they adhere to global_bounds and minimum_window.
106+
107+
Parameters:
108+
-----------
109+
new_bounds : np.array
110+
The proposed new_bounds that (may) need adjustment.
111+
112+
global_bounds : np.array
113+
The maximum allowable bounds for each parameter.
114+
115+
Returns:
116+
--------
117+
new_bounds : np.array
118+
The adjusted bounds after enforcing constraints.
119+
"""
120+
121+
#sort bounds
122+
new_bounds = np.sort(new_bounds)
123+
124+
# Validate each parameter's bounds against the global_bounds
125+
for i, pbounds in enumerate(new_bounds):
126+
# If the one of the bounds is outside the global bounds, reset the bound to the global bound
127+
# This is expected to happen when the window is near the global bounds, no warning is issued
128+
if (pbounds[0] < global_bounds[i, 0]):
129+
pbounds[0] = global_bounds[i, 0]
130+
131+
if (pbounds[1] > global_bounds[i, 1]):
132+
pbounds[1] = global_bounds[i, 1]
133+
134+
# If a lower bound is greater than the associated global upper bound, reset it to the global lower bound
135+
if (pbounds[0] > global_bounds[i, 1]):
136+
pbounds[0] = global_bounds[i, 0]
137+
warn("\nDomain Reduction Warning:\n"+
138+
"A parameter's lower bound is greater than the global upper bound."+
139+
"The offensive boundary has been reset."+
140+
"Be cautious of subsequent reductions.", stacklevel=2)
141+
142+
# If an upper bound is less than the associated global lower bound, reset it to the global upper bound
143+
if (pbounds[1] < global_bounds[i, 0]):
144+
pbounds[1] = global_bounds[i, 1]
145+
warn("\nDomain Reduction Warning:\n"+
146+
"A parameter's lower bound is greater than the global upper bound."+
147+
"The offensive boundary has been reset."+
148+
"Be cautious of subsequent reductions.", stacklevel=2)
149+
150+
# Adjust new_bounds to ensure they respect the minimum window width for each parameter
151+
for i, pbounds in enumerate(new_bounds):
152+
current_window_width = abs(pbounds[0] - pbounds[1])
153+
154+
# If the window width is less than the minimum allowable width, adjust it
155+
# Note that when minimum_window < width of the global bounds one side always has more space than required
156+
if current_window_width < self.minimum_window[i]:
157+
width_deficit = (self.minimum_window[i] - current_window_width) / 2.0
158+
available_left_space = abs(global_bounds[i, 0] - pbounds[0])
159+
available_right_space = abs(global_bounds[i, 1] - pbounds[1])
160+
161+
# determine how much to expand on the left and right
162+
expand_left = min(width_deficit, available_left_space)
163+
expand_right = min(width_deficit, available_right_space)
164+
165+
# calculate the deficit on each side
166+
expand_left_deficit = width_deficit - expand_left
167+
expand_right_deficit = width_deficit - expand_right
168+
169+
# shift the deficit to the side with more space
170+
adjust_left = expand_left + max(expand_right_deficit, 0)
171+
adjust_right = expand_right + max(expand_left_deficit, 0)
172+
173+
# adjust the bounds
174+
pbounds[0] -= adjust_left
175+
pbounds[1] += adjust_right
176+
122177
return new_bounds
123178

124179
def _window_bounds_compatibility(self, global_bounds: np.array) -> bool:
125-
"""Checks if global bounds are compatible with the minimum window sizes."""
180+
"""Checks if global bounds are compatible with the minimum window sizes.
181+
"""
126182
for i, entry in enumerate(global_bounds):
127183
global_window_width = abs(entry[1] - entry[0])
128184
if global_window_width < self.minimum_window[i]:
@@ -133,7 +189,8 @@ def _create_bounds(self, parameters: dict, bounds: np.array) -> dict:
133189
return {param: bounds[i, :] for i, param in enumerate(parameters)}
134190

135191
def transform(self, target_space: TargetSpace) -> dict:
136-
192+
"""Reduces the bounds of the target space.
193+
"""
137194
self._update(target_space)
138195

139196
new_bounds = np.array(
@@ -143,6 +200,6 @@ def transform(self, target_space: TargetSpace) -> dict:
143200
]
144201
).T
145202

146-
self._trim(new_bounds, self.original_bounds)
203+
new_bounds = self._trim(new_bounds, self.original_bounds)
147204
self.bounds.append(new_bounds)
148205
return self._create_bounds(target_space.keys, new_bounds)

0 commit comments

Comments
 (0)