hydra-optuna-sweeper 1.3.0.dev0__tar.gz → 1.4.0.dev5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (18) hide show
  1. hydra_optuna_sweeper-1.4.0.dev5/MANIFEST.in +3 -0
  2. {hydra-optuna-sweeper-1.3.0.dev0 → hydra_optuna_sweeper-1.4.0.dev5}/PKG-INFO +21 -7
  3. {hydra-optuna-sweeper-1.3.0.dev0 → hydra_optuna_sweeper-1.4.0.dev5}/hydra_optuna_sweeper.egg-info/PKG-INFO +21 -7
  4. {hydra-optuna-sweeper-1.3.0.dev0 → hydra_optuna_sweeper-1.4.0.dev5}/hydra_optuna_sweeper.egg-info/SOURCES.txt +3 -1
  5. {hydra-optuna-sweeper-1.3.0.dev0 → hydra_optuna_sweeper-1.4.0.dev5}/hydra_optuna_sweeper.egg-info/requires.txt +1 -0
  6. {hydra-optuna-sweeper-1.3.0.dev0 → hydra_optuna_sweeper-1.4.0.dev5}/hydra_plugins/hydra_optuna_sweeper/__init__.py +1 -1
  7. {hydra-optuna-sweeper-1.3.0.dev0 → hydra_optuna_sweeper-1.4.0.dev5}/hydra_plugins/hydra_optuna_sweeper/_impl.py +21 -11
  8. {hydra-optuna-sweeper-1.3.0.dev0 → hydra_optuna_sweeper-1.4.0.dev5}/hydra_plugins/hydra_optuna_sweeper/config.py +0 -1
  9. hydra_optuna_sweeper-1.4.0.dev5/hydra_plugins/hydra_optuna_sweeper/py.typed +0 -0
  10. {hydra-optuna-sweeper-1.3.0.dev0 → hydra_optuna_sweeper-1.4.0.dev5}/setup.py +7 -5
  11. hydra_optuna_sweeper-1.4.0.dev5/tests/test_optuna_sweeper_plugin.py +405 -0
  12. hydra-optuna-sweeper-1.3.0.dev0/MANIFEST.in +0 -3
  13. {hydra-optuna-sweeper-1.3.0.dev0 → hydra_optuna_sweeper-1.4.0.dev5}/README.md +0 -0
  14. {hydra-optuna-sweeper-1.3.0.dev0 → hydra_optuna_sweeper-1.4.0.dev5}/hydra_optuna_sweeper.egg-info/dependency_links.txt +0 -0
  15. {hydra-optuna-sweeper-1.3.0.dev0 → hydra_optuna_sweeper-1.4.0.dev5}/hydra_optuna_sweeper.egg-info/top_level.txt +0 -0
  16. {hydra-optuna-sweeper-1.3.0.dev0 → hydra_optuna_sweeper-1.4.0.dev5}/hydra_plugins/hydra_optuna_sweeper/optuna_sweeper.py +0 -0
  17. {hydra-optuna-sweeper-1.3.0.dev0 → hydra_optuna_sweeper-1.4.0.dev5}/pyproject.toml +0 -0
  18. {hydra-optuna-sweeper-1.3.0.dev0 → hydra_optuna_sweeper-1.4.0.dev5}/setup.cfg +0 -0
@@ -0,0 +1,3 @@
1
+ global-exclude *.pyc
2
+ global-exclude __pycache__
3
+ recursive-include hydra_plugins/* *.yaml py.typed
@@ -1,20 +1,34 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: hydra-optuna-sweeper
3
- Version: 1.3.0.dev0
3
+ Version: 1.4.0.dev5
4
4
  Summary: Hydra Optuna Sweeper plugin
5
5
  Home-page: https://github.com/facebookresearch/hydra/
6
6
  Author: Toshihiko Yanase, Hiroyuki Vincent Yamazaki
7
7
  Author-email: toshihiko.yanase@gmail.com, hiroyuki.vincent.yamazaki@gmail.com
8
- Classifier: License :: OSI Approved :: MIT License
9
- Classifier: Programming Language :: Python :: 3.6
10
- Classifier: Programming Language :: Python :: 3.7
11
- Classifier: Programming Language :: Python :: 3.8
12
- Classifier: Programming Language :: Python :: 3.9
8
+ License: MIT
13
9
  Classifier: Programming Language :: Python :: 3.10
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Classifier: Programming Language :: Python :: 3.14
14
14
  Classifier: Operating System :: POSIX :: Linux
15
15
  Classifier: Operating System :: MacOS
16
16
  Classifier: Development Status :: 4 - Beta
17
+ Requires-Python: >=3.10
17
18
  Description-Content-Type: text/markdown
19
+ Requires-Dist: hydra-core>=1.1.0.dev7
20
+ Requires-Dist: optuna<3.0.0,>=2.10.0
21
+ Requires-Dist: sqlalchemy~=1.3.0
22
+ Dynamic: author
23
+ Dynamic: author-email
24
+ Dynamic: classifier
25
+ Dynamic: description
26
+ Dynamic: description-content-type
27
+ Dynamic: home-page
28
+ Dynamic: license
29
+ Dynamic: requires-dist
30
+ Dynamic: requires-python
31
+ Dynamic: summary
18
32
 
19
33
  # Hydra Optuna Sweeper
20
34
 
@@ -1,20 +1,34 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: hydra-optuna-sweeper
3
- Version: 1.3.0.dev0
3
+ Version: 1.4.0.dev5
4
4
  Summary: Hydra Optuna Sweeper plugin
5
5
  Home-page: https://github.com/facebookresearch/hydra/
6
6
  Author: Toshihiko Yanase, Hiroyuki Vincent Yamazaki
7
7
  Author-email: toshihiko.yanase@gmail.com, hiroyuki.vincent.yamazaki@gmail.com
8
- Classifier: License :: OSI Approved :: MIT License
9
- Classifier: Programming Language :: Python :: 3.6
10
- Classifier: Programming Language :: Python :: 3.7
11
- Classifier: Programming Language :: Python :: 3.8
12
- Classifier: Programming Language :: Python :: 3.9
8
+ License: MIT
13
9
  Classifier: Programming Language :: Python :: 3.10
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Classifier: Programming Language :: Python :: 3.14
14
14
  Classifier: Operating System :: POSIX :: Linux
15
15
  Classifier: Operating System :: MacOS
16
16
  Classifier: Development Status :: 4 - Beta
17
+ Requires-Python: >=3.10
17
18
  Description-Content-Type: text/markdown
19
+ Requires-Dist: hydra-core>=1.1.0.dev7
20
+ Requires-Dist: optuna<3.0.0,>=2.10.0
21
+ Requires-Dist: sqlalchemy~=1.3.0
22
+ Dynamic: author
23
+ Dynamic: author-email
24
+ Dynamic: classifier
25
+ Dynamic: description
26
+ Dynamic: description-content-type
27
+ Dynamic: home-page
28
+ Dynamic: license
29
+ Dynamic: requires-dist
30
+ Dynamic: requires-python
31
+ Dynamic: summary
18
32
 
19
33
  # Hydra Optuna Sweeper
20
34
 
@@ -10,4 +10,6 @@ hydra_optuna_sweeper.egg-info/top_level.txt
10
10
  hydra_plugins/hydra_optuna_sweeper/__init__.py
11
11
  hydra_plugins/hydra_optuna_sweeper/_impl.py
12
12
  hydra_plugins/hydra_optuna_sweeper/config.py
13
- hydra_plugins/hydra_optuna_sweeper/optuna_sweeper.py
13
+ hydra_plugins/hydra_optuna_sweeper/optuna_sweeper.py
14
+ hydra_plugins/hydra_optuna_sweeper/py.typed
15
+ tests/test_optuna_sweeper_plugin.py
@@ -1,2 +1,3 @@
1
1
  hydra-core>=1.1.0.dev7
2
2
  optuna<3.0.0,>=2.10.0
3
+ sqlalchemy~=1.3.0
@@ -1,3 +1,3 @@
1
1
  # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
2
2
 
3
- __version__ = "1.3.0.dev0"
3
+ __version__ = "1.4.0.dev5"
@@ -49,7 +49,7 @@ log = logging.getLogger(__name__)
49
49
 
50
50
 
51
51
  def create_optuna_distribution_from_config(
52
- config: MutableMapping[str, Any]
52
+ config: MutableMapping[str, Any],
53
53
  ) -> BaseDistribution:
54
54
  kwargs = dict(config)
55
55
  if isinstance(config["type"], str):
@@ -107,7 +107,9 @@ def create_optuna_distribution_from_override(override: Override) -> Any:
107
107
  or isinstance(value.stop, float)
108
108
  or isinstance(value.step, float)
109
109
  ):
110
- return DiscreteUniformDistribution(value.start, value.stop, value.step)
110
+ return DiscreteUniformDistribution(
111
+ float(value.start), float(value.stop), float(value.step)
112
+ )
111
113
  return IntUniformDistribution(
112
114
  int(value.start), int(value.stop), step=int(value.step)
113
115
  )
@@ -130,20 +132,25 @@ def create_optuna_distribution_from_override(override: Override) -> Any:
130
132
 
131
133
  def create_params_from_overrides(
132
134
  arguments: List[str],
133
- ) -> Tuple[Dict[str, BaseDistribution], Dict[str, Any]]:
135
+ ) -> Tuple[Dict[str, BaseDistribution], Dict[str, Any], List[str]]:
134
136
  parser = OverridesParser.create()
135
137
  parsed = parser.parse_overrides(arguments)
136
138
  search_space_distributions = dict()
137
139
  fixed_params = dict()
140
+ fixed_overrides = []
138
141
 
139
142
  for override in parsed:
143
+ if override.is_delete():
144
+ assert override.input_line is not None
145
+ fixed_overrides.append(override.input_line)
146
+ continue
140
147
  param_name = override.get_key_element()
141
148
  value = create_optuna_distribution_from_override(override)
142
149
  if isinstance(value, BaseDistribution):
143
150
  search_space_distributions[param_name] = value
144
151
  else:
145
152
  fixed_params[param_name] = value
146
- return search_space_distributions, fixed_params
153
+ return search_space_distributions, fixed_params, fixed_overrides
147
154
 
148
155
 
149
156
  class OptunaSweeperImpl(Sweeper):
@@ -192,13 +199,11 @@ class OptunaSweeperImpl(Sweeper):
192
199
  )
193
200
  else:
194
201
  deprecation_warning(
195
- message=dedent(
196
- f"""\
202
+ message=dedent(f"""\
197
203
  `hydra.sweeper.search_space` is deprecated and will be removed in the next major release.
198
204
  Please configure with `hydra.sweeper.params`.
199
205
  {url}
200
- """
201
- ),
206
+ """),
202
207
  )
203
208
  self.search_space_distributions = {
204
209
  str(x): create_optuna_distribution_from_config(y)
@@ -232,6 +237,7 @@ class OptunaSweeperImpl(Sweeper):
232
237
  trials: List[Trial],
233
238
  search_space_distributions: Dict[str, BaseDistribution],
234
239
  fixed_params: Dict[str, Any],
240
+ fixed_overrides: List[str],
235
241
  ) -> Sequence[Sequence[str]]:
236
242
  overrides = []
237
243
  for trial in trials:
@@ -254,7 +260,10 @@ class OptunaSweeperImpl(Sweeper):
254
260
  params = dict(trial.params)
255
261
  params.update(fixed_params)
256
262
 
257
- overrides.append(tuple(f"{name}={val}" for name, val in params.items()))
263
+ overrides.append(
264
+ tuple(f"{name}={val}" for name, val in params.items())
265
+ + tuple(fixed_overrides)
266
+ )
258
267
  return overrides
259
268
 
260
269
  def _parse_sweeper_params_config(self) -> List[str]:
@@ -290,12 +299,13 @@ class OptunaSweeperImpl(Sweeper):
290
299
 
291
300
  is_grid_sampler = (
292
301
  isinstance(self.sampler, functools.partial)
293
- and self.sampler.func == optuna.samplers.GridSampler # type: ignore
302
+ and self.sampler.func == optuna.samplers.GridSampler
294
303
  )
295
304
 
296
305
  (
297
306
  override_search_space_distributions,
298
307
  fixed_params,
308
+ fixed_overrides,
299
309
  ) = create_params_from_overrides(params_conf)
300
310
 
301
311
  search_space_distributions = dict()
@@ -345,7 +355,7 @@ class OptunaSweeperImpl(Sweeper):
345
355
 
346
356
  trials = [study.ask() for _ in range(batch_size)]
347
357
  overrides = self._configure_trials(
348
- trials, search_space_distributions, fixed_params
358
+ trials, search_space_distributions, fixed_params, fixed_overrides
349
359
  )
350
360
 
351
361
  returns = self.launcher.launch(overrides, initial_job_idx=self.job_idx)
@@ -118,7 +118,6 @@ class MOTPESamplerConfig(SamplerConfig):
118
118
 
119
119
  @dataclass
120
120
  class DistributionConfig:
121
-
122
121
  # Type of distribution. "int", "float" or "categorical"
123
122
  type: DistributionType
124
123
 
@@ -11,24 +11,26 @@ setup(
11
11
  author="Toshihiko Yanase, Hiroyuki Vincent Yamazaki",
12
12
  author_email="toshihiko.yanase@gmail.com, hiroyuki.vincent.yamazaki@gmail.com",
13
13
  description="Hydra Optuna Sweeper plugin",
14
+ license="MIT",
14
15
  long_description=(Path(__file__).parent / "README.md").read_text(),
15
16
  long_description_content_type="text/markdown",
16
17
  url="https://github.com/facebookresearch/hydra/",
17
18
  packages=find_namespace_packages(include=["hydra_plugins.*"]),
18
19
  classifiers=[
19
- "License :: OSI Approved :: MIT License",
20
- "Programming Language :: Python :: 3.6",
21
- "Programming Language :: Python :: 3.7",
22
- "Programming Language :: Python :: 3.8",
23
- "Programming Language :: Python :: 3.9",
24
20
  "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Programming Language :: Python :: 3.14",
25
25
  "Operating System :: POSIX :: Linux",
26
26
  "Operating System :: MacOS",
27
27
  "Development Status :: 4 - Beta",
28
28
  ],
29
+ python_requires=">=3.10",
29
30
  install_requires=[
30
31
  "hydra-core>=1.1.0.dev7",
31
32
  "optuna>=2.10.0,<3.0.0",
33
+ "sqlalchemy~=1.3.0", # TODO: Unpin when upgrading to optuna v3.0
32
34
  ],
33
35
  include_package_data=True,
34
36
  )
@@ -0,0 +1,405 @@
1
+ # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
2
+ import os
3
+ import sys
4
+ from functools import partial
5
+ from pathlib import Path
6
+ from typing import Any, List, Optional
7
+
8
+ import optuna
9
+ from hydra.core.override_parser.overrides_parser import OverridesParser
10
+ from hydra.core.plugins import Plugins
11
+ from hydra.plugins.sweeper import Sweeper
12
+ from hydra.test_utils.test_utils import (
13
+ TSweepRunner,
14
+ chdir_plugin_root,
15
+ run_process,
16
+ run_python_script,
17
+ )
18
+ from omegaconf import DictConfig, OmegaConf
19
+ from optuna.distributions import (
20
+ BaseDistribution,
21
+ CategoricalDistribution,
22
+ DiscreteUniformDistribution,
23
+ IntLogUniformDistribution,
24
+ IntUniformDistribution,
25
+ LogUniformDistribution,
26
+ UniformDistribution,
27
+ )
28
+ from optuna.samplers import RandomSampler
29
+ from pytest import mark, warns
30
+
31
+ from hydra_plugins.hydra_optuna_sweeper import _impl
32
+ from hydra_plugins.hydra_optuna_sweeper._impl import OptunaSweeperImpl
33
+ from hydra_plugins.hydra_optuna_sweeper.config import Direction
34
+ from hydra_plugins.hydra_optuna_sweeper.optuna_sweeper import OptunaSweeper
35
+
36
+ chdir_plugin_root()
37
+
38
+ SQLALCHEMY_UTC_WARNING_FILTER = (
39
+ r"-W ignore:datetime.datetime.utcfromtimestamp() is deprecated:"
40
+ r"DeprecationWarning:sqlalchemy.sql.sqltypes"
41
+ )
42
+
43
+
44
+ def run_optuna_script(cmd: List[str]) -> None:
45
+ run_python_script([SQLALCHEMY_UTC_WARNING_FILTER, *cmd])
46
+
47
+
48
+ def test_discovery() -> None:
49
+ assert OptunaSweeper.__name__ in [
50
+ x.__name__ for x in Plugins.instance().discover(Sweeper)
51
+ ]
52
+
53
+
54
+ def check_distribution(expected: BaseDistribution, actual: BaseDistribution) -> None:
55
+ if not isinstance(expected, CategoricalDistribution):
56
+ assert expected == actual
57
+ return
58
+
59
+ assert isinstance(actual, CategoricalDistribution)
60
+ # shuffle() will randomize the order of items in choices.
61
+ assert set(expected.choices) == set(actual.choices)
62
+
63
+
64
+ @mark.parametrize(
65
+ "input, expected",
66
+ [
67
+ (
68
+ {"type": "categorical", "choices": [1, 2, 3]},
69
+ CategoricalDistribution([1, 2, 3]),
70
+ ),
71
+ ({"type": "int", "low": 0, "high": 10}, IntUniformDistribution(0, 10)),
72
+ (
73
+ {"type": "int", "low": 0, "high": 10, "step": 2},
74
+ IntUniformDistribution(0, 10, step=2),
75
+ ),
76
+ ({"type": "int", "low": 0, "high": 5}, IntUniformDistribution(0, 5)),
77
+ (
78
+ {"type": "int", "low": 1, "high": 100, "log": True},
79
+ IntLogUniformDistribution(1, 100),
80
+ ),
81
+ ({"type": "float", "low": 0, "high": 1}, UniformDistribution(0, 1)),
82
+ (
83
+ {"type": "float", "low": 0, "high": 10, "step": 2},
84
+ DiscreteUniformDistribution(0, 10, 2),
85
+ ),
86
+ (
87
+ {"type": "float", "low": 1, "high": 100, "log": True},
88
+ LogUniformDistribution(1, 100),
89
+ ),
90
+ ],
91
+ )
92
+ def test_create_optuna_distribution_from_config(input: Any, expected: Any) -> None:
93
+ actual = _impl.create_optuna_distribution_from_config(input)
94
+ check_distribution(expected, actual)
95
+
96
+
97
+ @mark.parametrize(
98
+ "input, expected",
99
+ [
100
+ ("key=choice(1,2)", CategoricalDistribution([1, 2])),
101
+ ("key=choice(true, false)", CategoricalDistribution([True, False])),
102
+ ("key=choice('hello', 'world')", CategoricalDistribution(["hello", "world"])),
103
+ ("key=shuffle(range(1,3))", CategoricalDistribution((1, 2))),
104
+ ("key=range(1,3)", IntUniformDistribution(1, 3)),
105
+ ("key=interval(1, 5)", UniformDistribution(1, 5)),
106
+ ("key=int(interval(1, 5))", IntUniformDistribution(1, 5)),
107
+ ("key=tag(log, interval(1, 5))", LogUniformDistribution(1, 5)),
108
+ ("key=tag(log, int(interval(1, 5)))", IntLogUniformDistribution(1, 5)),
109
+ ("key=range(0.5, 5.5, step=1)", DiscreteUniformDistribution(0.5, 5.5, 1)),
110
+ ],
111
+ )
112
+ def test_create_optuna_distribution_from_override(input: Any, expected: Any) -> None:
113
+ parser = OverridesParser.create()
114
+ parsed = parser.parse_overrides([input])[0]
115
+ actual = _impl.create_optuna_distribution_from_override(parsed)
116
+ check_distribution(expected, actual)
117
+
118
+
119
+ @mark.parametrize(
120
+ "input, expected",
121
+ [
122
+ (["key=choice(1,2)"], ({"key": CategoricalDistribution([1, 2])}, {}, [])),
123
+ (["key=5"], ({}, {"key": "5"}, [])),
124
+ (
125
+ ["key1=choice(1,2)", "key2=5"],
126
+ ({"key1": CategoricalDistribution([1, 2])}, {"key2": "5"}, []),
127
+ ),
128
+ (
129
+ ["key1=choice(1,2)", "key2=5", "key3=range(1,3)"],
130
+ (
131
+ {
132
+ "key1": CategoricalDistribution([1, 2]),
133
+ "key3": IntUniformDistribution(1, 3),
134
+ },
135
+ {"key2": "5"},
136
+ [],
137
+ ),
138
+ ),
139
+ (["~z"], ({}, {}, ["~z"])),
140
+ (["~z=10"], ({}, {}, ["~z=10"])),
141
+ ],
142
+ )
143
+ def test_create_params_from_overrides(input: Any, expected: Any) -> None:
144
+ actual = _impl.create_params_from_overrides(input)
145
+ assert actual == expected
146
+
147
+
148
+ def test_launch_jobs(hydra_sweep_runner: TSweepRunner) -> None:
149
+ sweep = hydra_sweep_runner(
150
+ calling_file=None,
151
+ calling_module="hydra.test_utils.a_module",
152
+ config_path="configs",
153
+ config_name="compose.yaml",
154
+ task_function=None,
155
+ overrides=[
156
+ "hydra/sweeper=optuna",
157
+ "hydra/launcher=basic",
158
+ "hydra.sweeper.n_trials=8",
159
+ "hydra.sweeper.n_jobs=3",
160
+ ],
161
+ )
162
+ with sweep:
163
+ assert sweep.returns is None
164
+
165
+
166
+ @mark.parametrize("with_commandline", (True, False))
167
+ def test_optuna_example(with_commandline: bool, tmpdir: Path) -> None:
168
+ storage = "sqlite:///" + os.path.join(str(tmpdir), "test.db")
169
+ study_name = "test-optuna-example"
170
+ cmd = [
171
+ "example/sphere.py",
172
+ "--multirun",
173
+ "hydra.sweep.dir=" + str(tmpdir),
174
+ "hydra.job.chdir=True",
175
+ "hydra.sweeper.n_trials=20",
176
+ "hydra.sweeper.n_jobs=1",
177
+ f"hydra.sweeper.storage={storage}",
178
+ f"hydra.sweeper.study_name={study_name}",
179
+ "hydra/sweeper/sampler=tpe",
180
+ "hydra.sweeper.sampler.seed=123",
181
+ "~z",
182
+ ]
183
+ if with_commandline:
184
+ cmd += [
185
+ "x=choice(0, 1, 2)",
186
+ "y=0", # Fixed parameter
187
+ ]
188
+ run_optuna_script(cmd)
189
+ returns = OmegaConf.load(f"{tmpdir}/optimization_results.yaml")
190
+ study = optuna.load_study(storage=storage, study_name=study_name)
191
+ best_trial = study.best_trial
192
+ assert isinstance(returns, DictConfig)
193
+ assert returns.name == "optuna"
194
+ assert returns["best_params"]["x"] == best_trial.params["x"]
195
+ if with_commandline:
196
+ assert "y" not in returns["best_params"]
197
+ assert "y" not in best_trial.params
198
+ else:
199
+ assert returns["best_params"]["y"] == best_trial.params["y"]
200
+ assert returns["best_value"] == best_trial.value
201
+ # Check the search performance of the TPE sampler.
202
+ # The threshold is the 95th percentile calculated with 1000 different seed values
203
+ # to make the test robust against the detailed implementation of the sampler.
204
+ # See https://github.com/facebookresearch/hydra/pull/1746#discussion_r681549830.
205
+ assert returns["best_value"] <= 2.27
206
+
207
+
208
+ @mark.parametrize("num_trials", (10, 1))
209
+ def test_example_with_grid_sampler(
210
+ tmpdir: Path,
211
+ num_trials: int,
212
+ ) -> None:
213
+ storage = "sqlite:///" + os.path.join(str(tmpdir), "test.db")
214
+ study_name = "test-grid-sampler"
215
+ cmd = [
216
+ "example/sphere.py",
217
+ "--multirun",
218
+ "--config-dir=tests/conf",
219
+ "--config-name=test_grid",
220
+ "hydra.sweep.dir=" + str(tmpdir),
221
+ "hydra.job.chdir=False",
222
+ f"hydra.sweeper.n_trials={num_trials}",
223
+ "hydra.sweeper.n_jobs=1",
224
+ f"hydra.sweeper.storage={storage}",
225
+ f"hydra.sweeper.study_name={study_name}",
226
+ ]
227
+ run_optuna_script(cmd)
228
+ returns = OmegaConf.load(f"{tmpdir}/optimization_results.yaml")
229
+ assert isinstance(returns, DictConfig)
230
+ bv, bx, by, bz = (
231
+ returns["best_value"],
232
+ returns["best_params"]["x"],
233
+ returns["best_params"]["y"],
234
+ returns["best_params"]["z"],
235
+ )
236
+ if num_trials >= 12:
237
+ assert bv == 1 and abs(bx) == 1 and by == 0
238
+ else:
239
+ assert bx in [-1, 1] and by in [-1, 0]
240
+ assert bz in ["foo", "bar"]
241
+
242
+
243
+ @mark.parametrize("with_commandline", (True, False))
244
+ def test_optuna_multi_objective_example(with_commandline: bool, tmpdir: Path) -> None:
245
+ cmd = [
246
+ "example/multi-objective.py",
247
+ "--multirun",
248
+ "hydra.sweep.dir=" + str(tmpdir),
249
+ "hydra.job.chdir=True",
250
+ "hydra.sweeper.n_trials=20",
251
+ "hydra.sweeper.n_jobs=1",
252
+ "hydra/sweeper/sampler=random",
253
+ "hydra.sweeper.sampler.seed=123",
254
+ ]
255
+ if with_commandline:
256
+ cmd += [
257
+ "x=range(0, 5)",
258
+ "y=range(0, 3)",
259
+ ]
260
+ run_optuna_script(cmd)
261
+ returns = OmegaConf.load(f"{tmpdir}/optimization_results.yaml")
262
+ assert isinstance(returns, DictConfig)
263
+ assert returns.name == "optuna"
264
+ if with_commandline:
265
+ for trial_x in returns["solutions"]:
266
+ assert trial_x["params"]["x"] % 1 == 0
267
+ assert trial_x["params"]["y"] % 1 == 0
268
+ # The trials must not dominate each other.
269
+ for trial_y in returns["solutions"]:
270
+ assert not _dominates(trial_x, trial_y)
271
+ else:
272
+ for trial_x in returns["solutions"]:
273
+ assert trial_x["params"]["x"] % 1 in {0, 0.5}
274
+ assert trial_x["params"]["y"] % 1 in {0, 0.5}
275
+ # The trials must not dominate each other.
276
+ for trial_y in returns["solutions"]:
277
+ assert not _dominates(trial_x, trial_y)
278
+
279
+
280
+ def _dominates(values_x: List[float], values_y: List[float]) -> bool:
281
+ return all(x <= y for x, y in zip(values_x, values_y)) and any(
282
+ x < y for x, y in zip(values_x, values_y)
283
+ )
284
+
285
+
286
+ def test_optuna_custom_search_space_example(tmpdir: Path) -> None:
287
+ max_z_difference_from_x = 0.3
288
+ cmd = [
289
+ "example/custom-search-space-objective.py",
290
+ "--multirun",
291
+ "hydra.sweep.dir=" + str(tmpdir),
292
+ "hydra.job.chdir=True",
293
+ "hydra.sweeper.n_trials=20",
294
+ "hydra.sweeper.n_jobs=1",
295
+ "hydra/sweeper/sampler=random",
296
+ "hydra.sweeper.sampler.seed=123",
297
+ f"max_z_difference_from_x={max_z_difference_from_x}",
298
+ ]
299
+ run_optuna_script(cmd)
300
+ returns = OmegaConf.load(f"{tmpdir}/optimization_results.yaml")
301
+ assert isinstance(returns, DictConfig)
302
+ assert returns.name == "optuna"
303
+ assert (
304
+ abs(returns["best_params"]["x"] - returns["best_params"]["z"])
305
+ <= max_z_difference_from_x
306
+ )
307
+ w = returns["best_params"]["+w"]
308
+ assert 0 <= w <= 1
309
+
310
+
311
+ @mark.parametrize(
312
+ "search_space,params,raise_warning,msg",
313
+ [
314
+ (None, None, False, None),
315
+ (
316
+ {},
317
+ {},
318
+ True,
319
+ r"Both hydra.sweeper.params and hydra.sweeper.search_space are configured.*",
320
+ ),
321
+ (
322
+ {},
323
+ None,
324
+ True,
325
+ r"`hydra.sweeper.search_space` is deprecated and will be removed in the next major release.*",
326
+ ),
327
+ (None, {}, False, None),
328
+ ],
329
+ )
330
+ def test_warnings(
331
+ tmpdir: Path,
332
+ search_space: Optional[DictConfig],
333
+ params: Optional[DictConfig],
334
+ raise_warning: bool,
335
+ msg: Optional[str],
336
+ ) -> None:
337
+ partial_sweeper = partial(
338
+ OptunaSweeperImpl,
339
+ sampler=RandomSampler(),
340
+ direction=Direction.minimize,
341
+ storage=None,
342
+ study_name="test",
343
+ n_trials=1,
344
+ n_jobs=1,
345
+ max_failure_rate=0.0,
346
+ custom_search_space=None,
347
+ )
348
+ if search_space is not None:
349
+ search_space = OmegaConf.create(search_space)
350
+ if params is not None:
351
+ params = OmegaConf.create(params)
352
+ sweeper = partial_sweeper(search_space=search_space, params=params)
353
+ if raise_warning:
354
+ with warns(
355
+ UserWarning,
356
+ match=msg,
357
+ ):
358
+ sweeper._process_searchspace_config()
359
+ else:
360
+ sweeper._process_searchspace_config()
361
+
362
+
363
+ @mark.parametrize("max_failure_rate", (0.5, 1.0))
364
+ def test_failure_rate(max_failure_rate: float, tmpdir: Path) -> None:
365
+ cmd = [
366
+ sys.executable,
367
+ "example/sphere.py",
368
+ "--multirun",
369
+ "hydra.sweep.dir=" + str(tmpdir),
370
+ "hydra.job.chdir=True",
371
+ "hydra.sweeper.n_trials=20",
372
+ "hydra.sweeper.n_jobs=2",
373
+ "hydra/sweeper/sampler=random",
374
+ "hydra.sweeper.sampler.seed=123",
375
+ f"hydra.sweeper.max_failure_rate={max_failure_rate}",
376
+ "error=true",
377
+ ]
378
+ out, err = run_process(cmd, print_error=False, raise_exception=False)
379
+ error_string = "RuntimeError: cfg.error is True"
380
+ if max_failure_rate < 1.0:
381
+ assert error_string in err
382
+ else:
383
+ assert error_string not in err
384
+
385
+
386
+ def test_example_with_deprecated_search_space(
387
+ tmpdir: Path,
388
+ ) -> None:
389
+ cmd = [
390
+ "-W ignore::UserWarning",
391
+ "example/sphere.py",
392
+ "--multirun",
393
+ "--config-dir=tests/conf",
394
+ "--config-name=test_deprecated_search_space",
395
+ "hydra.sweep.dir=" + str(tmpdir),
396
+ "hydra.job.chdir=True",
397
+ "hydra.sweeper.n_trials=20",
398
+ "hydra.sweeper.n_jobs=1",
399
+ ]
400
+
401
+ run_optuna_script(cmd)
402
+ returns = OmegaConf.load(f"{tmpdir}/optimization_results.yaml")
403
+ assert isinstance(returns, DictConfig)
404
+ assert returns.name == "optuna"
405
+ assert abs(returns["best_params"]["x"]) <= 5.5
@@ -1,3 +0,0 @@
1
- global-exclude *.pyc
2
- global-exclude __pycache__
3
- recursive-include hydra_plugins/* *.yaml