diff --git a/vizier/_src/algorithms/designers/harmonica.py b/vizier/_src/algorithms/designers/harmonica.py index f9cf188a9..efcf4b072 100644 --- a/vizier/_src/algorithms/designers/harmonica.py +++ b/vizier/_src/algorithms/designers/harmonica.py @@ -272,8 +272,14 @@ def __init__( f'This designer {self} does not support conditional search.' ) for p_config in problem_statement.search_space.parameters: - if p_config.external_type != vz.ExternalType.BOOLEAN: - raise ValueError('Only boolean search spaces are supported.') + if p_config.external_type != vz.ExternalType.BOOLEAN and ( + p_config.type == vz.ParameterType.DOUBLE + or len(p_config.feasible_values) != 2 + ): + raise ValueError( + 'Only boolean search spaces are supported, bot got parameter' + f' config: f{p_config}' + ) self._problem_statement = problem_statement self._metric_name = self._problem_statement.metric_information.item().name @@ -345,5 +351,11 @@ def suggest( parameters = vz.ParameterDict() for i, p in enumerate(self._search_space.parameters): - parameters[p.name] = 'True' if x_new[i] == 1.0 else 'False' + if p.external_type == vz.ExternalType.BOOLEAN: + parameters[p.name] = 'True' if x_new[i] == 1.0 else 'False' + else: + parameters[p.name] = ( + p.feasible_values[1] if x_new[i] == 1.0 else p.feasible_values[0] + ) + return [vz.TrialSuggestion(parameters=parameters)] diff --git a/vizier/_src/algorithms/designers/harmonica_test.py b/vizier/_src/algorithms/designers/harmonica_test.py index 79da37f0a..07986c4ba 100644 --- a/vizier/_src/algorithms/designers/harmonica_test.py +++ b/vizier/_src/algorithms/designers/harmonica_test.py @@ -15,9 +15,11 @@ from __future__ import annotations """Tests for bocs.""" +from vizier import pyvizier as vz from vizier._src.algorithms.designers import harmonica from vizier._src.algorithms.testing import test_runners from vizier._src.benchmarks.experimenters import combo_experimenter +from vizier.testing import test_studies from absl.testing import absltest @@ -39,6 +41,89 @@ def test_make_suggestions(self): validate_parameters=True) self.assertLen(trials, num_trials) + def test_make_suggestions_with_two_values(self): + space = vz.SearchSpace() + root = space.root + root.add_int_param('integer_0', 11, 12) + root.add_discrete_param('discrete_0', feasible_values=[1001, 2034]) + root.add_categorical_param('categorical_0', feasible_values=['a', 'b']) + root.add_bool_param('bool_0') + problem_statement = vz.ProblemStatement(space) + problem_statement.metric_information.append( + vz.MetricInformation( + name='main_objective', goal=vz.ObjectiveMetricGoal.MAXIMIZE + ) + ) + designer = harmonica.HarmonicaDesigner( + problem_statement, num_init_samples=1 + ) + num_trials = 10 + trials = test_runners.run_with_random_metrics( + designer, + problem_statement, + iters=num_trials, + batch_size=1, + verbose=1, + validate_parameters=True, + ) + self.assertLen(trials, num_trials) + + def test_categorical_search_space_with_more_than_two_values_raises_error( + self, + ): + experimenter = combo_experimenter.CentroidExperimenter(centroid_n_choice=3) + with self.assertRaisesRegex( + ValueError, 'Only boolean search spaces are supported' + ): + _ = harmonica.HarmonicaDesigner( + experimenter.problem_statement(), num_init_samples=1 + ) + + def test_integer_search_space_with_more_than_two_values_raises_error( + self, + ): + space = vz.SearchSpace() + root = space.root + root.add_int_param('integer_0', 11, 13) + problem_statement = vz.ProblemStatement(space) + problem_statement.metric_information.append( + vz.MetricInformation( + name='main_objective', goal=vz.ObjectiveMetricGoal.MAXIMIZE + ) + ) + with self.assertRaisesRegex( + ValueError, 'Only boolean search spaces are supported' + ): + _ = harmonica.HarmonicaDesigner(problem_statement, num_init_samples=1) + + def test_discrete_search_space_with_more_than_two_values_raises_error( + self, + ): + space = vz.SearchSpace() + root = space.root + root.add_discrete_param('discrete_0', feasible_values=[1001, 2034, 3000]) + problem_statement = vz.ProblemStatement(space) + problem_statement.metric_information.append( + vz.MetricInformation( + name='main_objective', goal=vz.ObjectiveMetricGoal.MAXIMIZE + ) + ) + with self.assertRaisesRegex( + ValueError, 'Only boolean search spaces are supported' + ): + _ = harmonica.HarmonicaDesigner(problem_statement, num_init_samples=1) + + def test_continuous_search_space_raises_error(self): + with self.assertRaisesRegex( + ValueError, 'Only boolean search spaces are supported' + ): + _ = harmonica.HarmonicaDesigner( + vz.ProblemStatement( + test_studies.flat_continuous_space_with_scaling() + ), + num_init_samples=1, + ) + if __name__ == '__main__': absltest.main()