Skip to content

Commit e078433

Browse files
Partially fix #412 by adding a dedicated method to evaluating y_max (#415)
* Partially Fix #412 by adding a dedicated method to evaluating y_max * Fix typos * Rename `y_max()` to `_target_max()` * Return `None` when there is no feasible point in the target space. * Adapt the tests to the new TargetSpace.max behaviour · Issue #412 Now the TargetSpace.max returns None if there is no sampled feasible point or no point was sampled at all.
1 parent 04aea8b commit e078433

File tree

5 files changed

+75
-39
lines changed

5 files changed

+75
-39
lines changed

bayes_opt/bayesian_optimization.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ class BayesianOptimization(Observable):
9595
If True, the optimizer will allow duplicate points to be registered.
9696
This behavior may be desired in high noise situations where repeatedly probing
9797
the same point will give different answers. In other situations, the acquisition
98-
may occasionaly generate a duplicate point.
98+
may occasionally generate a duplicate point.
9999
100100
Methods
101101
-------
@@ -226,7 +226,7 @@ def suggest(self, utility_function):
226226
suggestion = acq_max(ac=utility_function.utility,
227227
gp=self._gp,
228228
constraint=self.constraint,
229-
y_max=self._space.target.max(),
229+
y_max=self._space._target_max(),
230230
bounds=self._space.bounds,
231231
random_state=self._random_state)
232232

@@ -276,7 +276,7 @@ def maximize(self,
276276
An instance of bayes_opt.util.UtilityFunction.
277277
If nothing is passed, a default using ucb is used
278278
279-
All other parameters are unused, and are only available to ensure backwards compatability - these
279+
All other parameters are unused, and are only available to ensure backwards compatibility - these
280280
will be removed in a future release
281281
"""
282282
self._prime_subscriptions()

bayes_opt/logger.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ def _header(self, instance):
106106
return line + "\n" + ("-" * self._header_length)
107107

108108
def _is_new_max(self, instance):
109-
if instance.max["target"] is None:
109+
if instance.max is None:
110110
# During constrained optimization, there might not be a maximum
111111
# value since the optimizer might've not encountered any points
112112
# that fulfill the constraints.

bayes_opt/observer.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,12 @@ def __init__(self):
2323
def _update_tracker(self, event, instance):
2424
if event == Events.OPTIMIZATION_STEP:
2525
self._iterations += 1
26+
27+
if instance.max is None:
28+
return
2629

2730
current_max = instance.max
31+
2832
if (self._previous_max is None
2933
or current_max["target"] > self._previous_max):
3034
self._previous_max = current_max["target"]

bayes_opt/target_space.py

Lines changed: 36 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ def register(self, params, target, constraint_value=None):
214214

215215
def probe(self, params):
216216
"""
217-
Evaulates a single point x, to obtain the value y and then records them
217+
Evaluates a single point x, to obtain the value y and then records them
218218
as observations.
219219
220220
Notes
@@ -265,44 +265,46 @@ def random_sample(self):
265265
data.T[col] = self.random_state.uniform(lower, upper, size=1)
266266
return data.ravel()
267267

268+
def _target_max(self):
269+
"""Get maximum target value found.
270+
271+
If there is a constraint present, the maximum value that fulfills the
272+
constraint is returned."""
273+
if len(self.target) == 0:
274+
return None
275+
276+
if self._constraint is None:
277+
return self.target.max()
278+
279+
allowed = self._constraint.allowed(self._constraint_values)
280+
if allowed.any():
281+
return self.target[allowed].max()
282+
283+
return None
284+
268285
def max(self):
269286
"""Get maximum target value found and corresponding parameters.
270287
271288
If there is a constraint present, the maximum value that fulfills the
272289
constraint is returned."""
273-
if self._constraint is None:
274-
try:
275-
res = {
276-
'target': self.target.max(),
277-
'params': dict(
278-
zip(self.keys, self.params[self.target.argmax()])
279-
)
280-
}
281-
except ValueError:
282-
res = {}
283-
return res
284-
else:
285-
allowed = self._constraint.allowed(self._constraint_values)
286-
if allowed.any():
287-
# Getting of all points that fulfill the constraints, find the
288-
# one with the maximum value for the target function.
289-
sorted = np.argsort(self.target)
290-
idx = sorted[allowed[sorted]][-1]
291-
# there must be a better way to do this, right?
292-
res = {
293-
'target': self.target[idx],
294-
'params': dict(
295-
zip(self.keys, self.params[idx])
296-
),
297-
'constraint': self._constraint_values[idx]
298-
}
299-
else:
300-
res = {
301-
'target': None,
302-
'params': None,
303-
'constraint': None
304-
}
305-
return res
290+
target_max = self._target_max()
291+
292+
if target_max is None:
293+
return None
294+
295+
target_max_idx = np.where(self.target == target_max)[0][0]
296+
297+
res = {
298+
'target': target_max,
299+
'params': dict(
300+
zip(self.keys, self.params[target_max_idx])
301+
)
302+
}
303+
304+
if self._constraint is not None:
305+
res['constraint'] = self._constraint_values[target_max_idx]
306+
307+
return res
306308

307309
def res(self):
308310
"""Get all target values and constraint fulfillment for all parameters.

tests/test_target_space.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,16 +163,46 @@ def test_random_sample():
163163
assert all(random_sample <= space.bounds[:, 1])
164164

165165

166+
def test_y_max():
167+
space = TargetSpace(target_func, PBOUNDS)
168+
assert space._target_max() == None
169+
space.probe(params={"p1": 1, "p2": 2})
170+
space.probe(params={"p1": 5, "p2": 1})
171+
space.probe(params={"p1": 0, "p2": 1})
172+
assert space._target_max() == 6
173+
174+
def test_y_max_with_constraint():
175+
constraint = ConstraintModel(lambda p1, p2: p1-p2, -2, 2)
176+
space = TargetSpace(target_func, PBOUNDS, constraint)
177+
assert space._target_max() == None
178+
space.probe(params={"p1": 1, "p2": 2}) # Feasible
179+
space.probe(params={"p1": 5, "p2": 1}) # Unfeasible
180+
space.probe(params={"p1": 0, "p2": 1}) # Feasible
181+
assert space._target_max() == 3
182+
183+
184+
166185
def test_max():
167186
space = TargetSpace(target_func, PBOUNDS)
168187

169-
assert space.max() == {}
188+
assert space.max() == None
170189
space.probe(params={"p1": 1, "p2": 2})
171190
space.probe(params={"p1": 5, "p2": 4})
172191
space.probe(params={"p1": 2, "p2": 3})
173192
space.probe(params={"p1": 1, "p2": 6})
174193
assert space.max() == {"params": {"p1": 5, "p2": 4}, "target": 9}
175194

195+
def test_max_with_constraint():
196+
constraint = ConstraintModel(lambda p1, p2: p1-p2, -2, 2)
197+
space = TargetSpace(target_func, PBOUNDS, constraint=constraint)
198+
199+
assert space.max() == None
200+
space.probe(params={"p1": 1, "p2": 2}) # Feasible
201+
space.probe(params={"p1": 5, "p2": 8}) # Unfeasible
202+
space.probe(params={"p1": 2, "p2": 3}) # Feasible
203+
space.probe(params={"p1": 1, "p2": 6}) # Unfeasible
204+
assert space.max() == {"params": {"p1": 2, "p2": 3}, "target": 5, "constraint": -1}
205+
176206

177207
def test_res():
178208
space = TargetSpace(target_func, PBOUNDS)

0 commit comments

Comments
 (0)