diff --git a/bayes_opt/acquisition.py b/bayes_opt/acquisition.py index 546ca7be..4c8c7191 100644 --- a/bayes_opt/acquisition.py +++ b/bayes_opt/acquisition.py @@ -452,6 +452,14 @@ def __init__( if kappa < 0: error_msg = "kappa must be greater than or equal to 0." raise ValueError(error_msg) + if exploration_decay is not None and not (0 < exploration_decay <= 1): + error_msg = "exploration_decay must be greater than 0 and less than or equal to 1." + raise ValueError(error_msg) + if exploration_decay_delay is not None and ( + not isinstance(exploration_decay_delay, int) or exploration_decay_delay < 0 + ): + error_msg = "exploration_decay_delay must be an integer greater than or equal to 0." + raise ValueError(error_msg) super().__init__(random_state=random_state) self.kappa = kappa @@ -604,6 +612,18 @@ def __init__( exploration_decay_delay: int | None = None, random_state: int | RandomState | None = None, ) -> None: + if xi < 0: + error_msg = "xi must be greater than or equal to 0." + raise ValueError(error_msg) + if exploration_decay is not None and not (0 < exploration_decay <= 1): + error_msg = "exploration_decay must be greater than 0 and less than or equal to 1." + raise ValueError(error_msg) + if exploration_decay_delay is not None and ( + not isinstance(exploration_decay_delay, int) or exploration_decay_delay < 0 + ): + error_msg = "exploration_decay_delay must be an integer greater than or equal to 0." + raise ValueError(error_msg) + super().__init__(random_state=random_state) self.xi = xi self.exploration_decay = exploration_decay @@ -766,6 +786,7 @@ class ExpectedImprovement(AcquisitionFunction): Decay rate for xi. If None, no decay is applied. exploration_decay_delay : int, default None + Delay for decay. If None, decay is applied from the start. random_state : int, RandomState, default None Set the random state for reproducibility. @@ -778,6 +799,18 @@ def __init__( exploration_decay_delay: int | None = None, random_state: int | RandomState | None = None, ) -> None: + if xi < 0: + error_msg = "xi must be greater than or equal to 0." + raise ValueError(error_msg) + if exploration_decay is not None and not (0 < exploration_decay <= 1): + error_msg = "exploration_decay must be greater than 0 and less than or equal to 1." + raise ValueError(error_msg) + if exploration_decay_delay is not None and ( + not isinstance(exploration_decay_delay, int) or exploration_decay_delay < 0 + ): + error_msg = "exploration_decay_delay must be an integer greater than or equal to 0." + raise ValueError(error_msg) + super().__init__(random_state=random_state) self.xi = xi self.exploration_decay = exploration_decay diff --git a/bayes_opt/bayesian_optimization.py b/bayes_opt/bayesian_optimization.py index 443cdc06..0df53b6d 100644 --- a/bayes_opt/bayesian_optimization.py +++ b/bayes_opt/bayesian_optimization.py @@ -55,6 +55,11 @@ class BayesianOptimization: Dictionary with parameters names as keys and a tuple with minimum and maximum values. + acquisition_function: AcquisitionFunction, optional(default=None) + The acquisition function to use for suggesting new points to evaluate. + If None, defaults to UpperConfidenceBound for unconstrained problems + and ExpectedImprovement for constrained problems. + constraint: NonlinearConstraint. Note that the names of arguments of the constraint function and of f need to be the same. diff --git a/tests/test_acquisition.py b/tests/test_acquisition.py index 30150bcf..efb7bb5f 100644 --- a/tests/test_acquisition.py +++ b/tests/test_acquisition.py @@ -377,6 +377,66 @@ def test_upper_confidence_bound_invalid_kappa_error(kappa: float): acquisition.UpperConfidenceBound(kappa=kappa) +@pytest.mark.parametrize("exploration_decay", [-0.1, 0.0, 1.1, 2.0, np.inf]) +def test_upper_confidence_bound_invalid_exploration_decay_error(exploration_decay: float): + with pytest.raises( + ValueError, match="exploration_decay must be greater than 0 and less than or equal to 1." + ): + acquisition.UpperConfidenceBound(kappa=1.0, exploration_decay=exploration_decay) + + +@pytest.mark.parametrize("exploration_decay_delay", [-1, -10, "not_an_int", 1.5]) +def test_upper_confidence_bound_invalid_exploration_decay_delay_error(exploration_decay_delay): + with pytest.raises( + ValueError, match="exploration_decay_delay must be an integer greater than or equal to 0." + ): + acquisition.UpperConfidenceBound(kappa=1.0, exploration_decay_delay=exploration_decay_delay) + + +@pytest.mark.parametrize("xi", [-0.1, -1.0, -np.inf]) +def test_probability_of_improvement_invalid_xi_error(xi: float): + with pytest.raises(ValueError, match="xi must be greater than or equal to 0."): + acquisition.ProbabilityOfImprovement(xi=xi) + + +@pytest.mark.parametrize("exploration_decay", [-0.1, 0.0, 1.1, 2.0, np.inf]) +def test_probability_of_improvement_invalid_exploration_decay_error(exploration_decay: float): + with pytest.raises( + ValueError, match="exploration_decay must be greater than 0 and less than or equal to 1." + ): + acquisition.ProbabilityOfImprovement(xi=0.01, exploration_decay=exploration_decay) + + +@pytest.mark.parametrize("exploration_decay_delay", [-1, -10, "not_an_int", 1.5]) +def test_probability_of_improvement_invalid_exploration_decay_delay_error(exploration_decay_delay): + with pytest.raises( + ValueError, match="exploration_decay_delay must be an integer greater than or equal to 0." + ): + acquisition.ProbabilityOfImprovement(xi=0.01, exploration_decay_delay=exploration_decay_delay) + + +@pytest.mark.parametrize("xi", [-0.1, -1.0, -np.inf]) +def test_expected_improvement_invalid_xi_error(xi: float): + with pytest.raises(ValueError, match="xi must be greater than or equal to 0."): + acquisition.ExpectedImprovement(xi=xi) + + +@pytest.mark.parametrize("exploration_decay", [-0.1, 0.0, 1.1, 2.0, np.inf]) +def test_expected_improvement_invalid_exploration_decay_error(exploration_decay: float): + with pytest.raises( + ValueError, match="exploration_decay must be greater than 0 and less than or equal to 1." + ): + acquisition.ExpectedImprovement(xi=0.01, exploration_decay=exploration_decay) + + +@pytest.mark.parametrize("exploration_decay_delay", [-1, -10, "not_an_int", 1.5]) +def test_expected_improvement_invalid_exploration_decay_delay_error(exploration_decay_delay): + with pytest.raises( + ValueError, match="exploration_decay_delay must be an integer greater than or equal to 0." + ): + acquisition.ExpectedImprovement(xi=0.01, exploration_decay_delay=exploration_decay_delay) + + def verify_optimizers_match(optimizer1, optimizer2): """Helper function to verify two optimizers match.""" assert len(optimizer1.space) == len(optimizer2.space)