diff --git a/botorch/optim/optimize.py b/botorch/optim/optimize.py index 61e44d32b6..203da97c26 100644 --- a/botorch/optim/optimize.py +++ b/botorch/optim/optimize.py @@ -25,6 +25,7 @@ qHypervolumeKnowledgeGradient, ) from botorch.exceptions import InputDataError, UnsupportedError +from botorch.exceptions.errors import CandidateGenerationError from botorch.exceptions.warnings import OptimizationWarning from botorch.generation.gen import gen_candidates_scipy, TGenCandidates from botorch.logging import logger @@ -938,27 +939,46 @@ def optimize_acqf_mixed( if q == 1: ff_candidate_list, ff_acq_value_list = [], [] + num_candidate_generation_failures = 0 for fixed_features in fixed_features_list: - candidate, acq_value = optimize_acqf( - acq_function=acq_function, - bounds=bounds, - q=q, - num_restarts=num_restarts, - raw_samples=raw_samples, - options=options or {}, - inequality_constraints=inequality_constraints, - equality_constraints=equality_constraints, - nonlinear_inequality_constraints=nonlinear_inequality_constraints, - fixed_features=fixed_features, - post_processing_func=post_processing_func, - batch_initial_conditions=batch_initial_conditions, - ic_generator=ic_generator, - return_best_only=True, - **ic_gen_kwargs, - ) + try: + candidate, acq_value = optimize_acqf( + acq_function=acq_function, + bounds=bounds, + q=q, + num_restarts=num_restarts, + raw_samples=raw_samples, + options=options or {}, + inequality_constraints=inequality_constraints, + equality_constraints=equality_constraints, + nonlinear_inequality_constraints=nonlinear_inequality_constraints, + fixed_features=fixed_features, + post_processing_func=post_processing_func, + batch_initial_conditions=batch_initial_conditions, + ic_generator=ic_generator, + return_best_only=True, + **ic_gen_kwargs, + ) + except CandidateGenerationError: + # if candidate generation fails, we skip this candidate + num_candidate_generation_failures += 1 + continue ff_candidate_list.append(candidate) ff_acq_value_list.append(acq_value) + if len(ff_candidate_list) == 0: + raise CandidateGenerationError( + "Candidate generation failed for all `fixed_features`." + ) + elif num_candidate_generation_failures > 0: + warnings.warn( + f"Candidate generation failed for {num_candidate_generation_failures} " + "combinations of `fixed_features`. To suppress this warning, make " + "sure all equality/inequality constraints can be satisfied by all " + "`fixed_features` in `fixed_features_list`.", + OptimizationWarning, + stacklevel=3, + ) ff_acq_values = torch.stack(ff_acq_value_list) best = torch.argmax(ff_acq_values) return ff_candidate_list[best], ff_acq_values[best] diff --git a/test/optim/test_optimize.py b/test/optim/test_optimize.py index c634ce4ef9..fad021c61b 100644 --- a/test/optim/test_optimize.py +++ b/test/optim/test_optimize.py @@ -23,6 +23,7 @@ qHypervolumeKnowledgeGradient, ) from botorch.exceptions import InputDataError, UnsupportedError +from botorch.exceptions.errors import CandidateGenerationError from botorch.exceptions.warnings import OptimizationWarning from botorch.generation.gen import gen_candidates_scipy, gen_candidates_torch from botorch.models import SingleTaskGP @@ -1588,6 +1589,41 @@ def test_optimize_acqf_one_shot_large_q(self): raw_samples=10, ) + def test_optimize_acqf_mixed_ff_with_constraint(self): + mock_acq_function = MockAcquisitionFunction() + bounds = torch.stack([torch.zeros(3), 4 * torch.ones(3)]) + ineq_constraints = [(torch.zeros(1), torch.ones(1), 1)] # x[0] >= 1 + with self.assertWarnsRegex( + OptimizationWarning, + "Candidate generation failed for 1 combinations of `fixed_features`. " + "To suppress this warning, make sure all equality/inequality " + "constraints can be satisfied by all `fixed_features` in " + "`fixed_features_list`.", + ): + optimize_acqf_mixed( + acq_function=mock_acq_function, + q=1, + fixed_features_list=[{0: 0}, {0: 1}], + bounds=bounds, + num_restarts=2, + raw_samples=10, + inequality_constraints=ineq_constraints, + ) + # No fixed features satisfy the constraint + with self.assertRaisesRegex( + CandidateGenerationError, + "Candidate generation failed for all `fixed_features`.", + ): + optimize_acqf_mixed( + acq_function=mock_acq_function, + q=1, + fixed_features_list=[{0: 0}], + bounds=bounds, + num_restarts=2, + raw_samples=10, + inequality_constraints=ineq_constraints, + ) + class TestOptimizeAcqfDiscrete(BotorchTestCase): def test_optimize_acqf_discrete(self):