Skip to content

Commit 5050e9f

Browse files
authored
Feature/auto compress pickle study (#107)
* move study save and load to io module * lzma compress study pickle --------- Signed-off-by: Grossberger Lukas (CR/AIR2.2) <Lukas.Grossberger@de.bosch.com>
1 parent 96b2ac5 commit 5050e9f

File tree

6 files changed

+196
-178
lines changed

6 files changed

+196
-178
lines changed

blackboxopt/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from parameterspace import ParameterSpace
44

5+
from . import io
56
from .base import (
67
ConstraintsError,
78
ContextError,

blackboxopt/io.py

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# Copyright (c) 2023 - for information on the respective copyright owner
2+
# see the NOTICE file and/or the repository https://github.com/boschresearch/blackboxopt
3+
#
4+
# SPDX-License-Identifier: Apache-2.0
5+
6+
import json
7+
import lzma
8+
import os
9+
import pickle
10+
from pathlib import Path
11+
from typing import List, Tuple
12+
13+
import parameterspace as ps
14+
15+
from blackboxopt.base import Objective
16+
from blackboxopt.evaluation import Evaluation
17+
18+
19+
def save_study_as_json(
20+
search_space: ps.ParameterSpace,
21+
objectives: List[Objective],
22+
evaluations: List[Evaluation],
23+
json_file_path: os.PathLike,
24+
overwrite: bool = False,
25+
):
26+
"""Save space, objectives and evaluations as json at `json_file_path`."""
27+
_file_path = Path(json_file_path)
28+
if not _file_path.parent.exists():
29+
raise IOError(
30+
f"The parent directory for {_file_path} does not exist, please create it."
31+
)
32+
if _file_path.exists() and not overwrite:
33+
raise IOError(f"{_file_path} exists and overwrite is False")
34+
35+
with open(_file_path, "w", encoding="UTF-8") as fh:
36+
json.dump(
37+
{
38+
"search_space": search_space.to_dict(),
39+
"objectives": [o.__dict__ for o in objectives],
40+
"evaluations": [e.__dict__ for e in evaluations],
41+
},
42+
fh,
43+
)
44+
45+
46+
def load_study_from_json(
47+
json_file_path: os.PathLike,
48+
) -> Tuple[ps.ParameterSpace, List[Objective], List[Evaluation]]:
49+
"""Load space, objectives and evaluations from a given `json_file_path`."""
50+
with open(json_file_path, "r", encoding="UTF-8") as fh:
51+
study = json.load(fh)
52+
53+
search_space = ps.ParameterSpace.from_dict(study["search_space"])
54+
objectives = [Objective(**o) for o in study["objectives"]]
55+
evaluations = [Evaluation(**e) for e in study["evaluations"]]
56+
57+
return search_space, objectives, evaluations
58+
59+
60+
def save_study_as_pickle(
61+
search_space: ps.ParameterSpace,
62+
objectives: List[Objective],
63+
evaluations: List[Evaluation],
64+
pickle_file_path: os.PathLike,
65+
overwrite: bool = False,
66+
):
67+
"""Save space, objectives and evaluations as an lzma compressed pickle."""
68+
_file_path = Path(pickle_file_path)
69+
if not _file_path.parent.exists():
70+
raise IOError(
71+
f"The parent directory for {_file_path} does not exist, please create it."
72+
)
73+
if _file_path.exists() and not overwrite:
74+
raise IOError(f"{_file_path} exists and overwrite is False")
75+
76+
with lzma.open(_file_path, "wb") as fh:
77+
pickle.dump(
78+
{
79+
"search_space": search_space,
80+
"objectives": objectives,
81+
"evaluations": evaluations,
82+
},
83+
fh,
84+
)
85+
86+
87+
def load_study_from_pickle(
88+
pickle_file_path: os.PathLike,
89+
) -> Tuple[ps.ParameterSpace, List[Objective], List[Evaluation]]:
90+
"""Load space, objectives and evaluations from a given lzma compressed pickle."""
91+
with lzma.open(pickle_file_path, "rb") as fh:
92+
study = pickle.load(fh)
93+
94+
return study["search_space"], study["objectives"], study["evaluations"]

blackboxopt/utils.py

+1-83
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,11 @@
44
# SPDX-License-Identifier: Apache-2.0
55

66
import hashlib
7-
import json
8-
import os
97
import pickle
108
from itertools import compress
11-
from pathlib import Path
12-
from typing import Dict, Iterable, List, Optional, Sequence, Tuple
9+
from typing import Dict, Iterable, List, Optional, Sequence
1310

1411
import numpy as np
15-
import parameterspace as ps
1612

1713
from blackboxopt.base import Objective
1814
from blackboxopt.evaluation import Evaluation
@@ -103,81 +99,3 @@ def sort_evaluations(evaluations: Iterable[Evaluation]) -> Iterable[Evaluation]:
10399
)
104100
).hexdigest(),
105101
)
106-
107-
108-
def save_study_as_json(
109-
search_space: ps.ParameterSpace,
110-
objectives: List[Objective],
111-
evaluations: List[Evaluation],
112-
json_file_path: os.PathLike,
113-
overwrite: bool = False,
114-
):
115-
"""Save space, objectives and evaluations as json at `json_file_path`."""
116-
_file_path = Path(json_file_path)
117-
if not _file_path.parent.exists():
118-
raise IOError(
119-
f"The parent directory for {_file_path} does not exist, please create it."
120-
)
121-
if _file_path.exists() and not overwrite:
122-
raise IOError(f"{_file_path} exists and overwrite is False")
123-
124-
with open(_file_path, "w", encoding="UTF-8") as fh:
125-
json.dump(
126-
{
127-
"search_space": search_space.to_dict(),
128-
"objectives": [o.__dict__ for o in objectives],
129-
"evaluations": [e.__dict__ for e in evaluations],
130-
},
131-
fh,
132-
)
133-
134-
135-
def load_study_from_json(
136-
json_file_path: os.PathLike,
137-
) -> Tuple[ps.ParameterSpace, List[Objective], List[Evaluation]]:
138-
"""Load space, objectives and evaluations from a given `json_file_path`."""
139-
with open(json_file_path, "r", encoding="UTF-8") as fh:
140-
study = json.load(fh)
141-
142-
search_space = ps.ParameterSpace.from_dict(study["search_space"])
143-
objectives = [Objective(**o) for o in study["objectives"]]
144-
evaluations = [Evaluation(**e) for e in study["evaluations"]]
145-
146-
return search_space, objectives, evaluations
147-
148-
149-
def save_study_as_pickle(
150-
search_space: ps.ParameterSpace,
151-
objectives: List[Objective],
152-
evaluations: List[Evaluation],
153-
pickle_file_path: os.PathLike,
154-
overwrite: bool = False,
155-
):
156-
"""Save space, objectives and evaluations as pickle at `pickle_file_path`."""
157-
_file_path = Path(pickle_file_path)
158-
if not _file_path.parent.exists():
159-
raise IOError(
160-
f"The parent directory for {_file_path} does not exist, please create it."
161-
)
162-
if _file_path.exists() and not overwrite:
163-
raise IOError(f"{_file_path} exists and overwrite is False")
164-
165-
with open(_file_path, "wb") as fh:
166-
pickle.dump(
167-
{
168-
"search_space": search_space,
169-
"objectives": objectives,
170-
"evaluations": evaluations,
171-
},
172-
fh,
173-
)
174-
175-
176-
def load_study_from_pickle(
177-
pickle_file_path: os.PathLike,
178-
) -> Tuple[ps.ParameterSpace, List[Objective], List[Evaluation]]:
179-
"""Load space, objectives and evaluations from a given `pickle_file_path`."""
180-
with open(pickle_file_path, "rb") as fh:
181-
study = pickle.load(fh)
182-
183-
return study["search_space"], study["objectives"], study["evaluations"]

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "blackboxopt"
3-
version = "4.15.1"
3+
version = "5.0.0"
44
description = "A common interface for blackbox optimization algorithms along with useful helpers like parallel optimization loops, analysis and visualization scripts."
55
readme = "README.md"
66
repository = "https://github.com/boschresearch/blackboxopt"

tests/io_test.py

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# Copyright (c) 2023 - for information on the respective copyright owner
2+
# see the NOTICE file and/or the repository https://github.com/boschresearch/blackboxopt
3+
#
4+
# SPDX-License-Identifier: Apache-2.0
5+
6+
7+
import parameterspace as ps
8+
import pytest
9+
10+
import blackboxopt as bbo
11+
12+
13+
def test_save_and_load_study_pickle(tmp_path):
14+
tmp_file = tmp_path / "out.json"
15+
16+
search_space = ps.ParameterSpace()
17+
search_space.add(ps.IntegerParameter("p", (-10, 10)))
18+
objectives = [bbo.Objective("loss", False), bbo.Objective("score", True)]
19+
evaluations = [
20+
bbo.Evaluation(configuration={"p": 1}, objectives={"loss": 1.0, "score": 3.0}),
21+
bbo.Evaluation(configuration={"p": 2}, objectives={"loss": 0.1, "score": 2.0}),
22+
bbo.Evaluation(configuration={"p": 3}, objectives={"loss": 0.0, "score": 1.0}),
23+
]
24+
bbo.io.save_study_as_pickle(search_space, objectives, evaluations, tmp_file)
25+
26+
# Check that default overwrite=False causes IOError on existing file
27+
with pytest.raises(IOError, match=str(tmp_file)):
28+
bbo.io.save_study_as_pickle(search_space, objectives, evaluations, tmp_file)
29+
30+
loaded_study = bbo.io.load_study_from_pickle(tmp_file)
31+
32+
assert loaded_study[1] == objectives
33+
assert loaded_study[2] == evaluations
34+
for _ in range(128):
35+
assert search_space.sample() == loaded_study[0].sample()
36+
37+
38+
def test_save_and_load_study_pickle_fails_on_missing_output_directory():
39+
pickle_file_path = "/this/directory/does/not/exist/pickles/out.pkl"
40+
with pytest.raises(IOError, match=pickle_file_path):
41+
bbo.io.save_study_as_pickle(
42+
search_space=ps.ParameterSpace(),
43+
objectives=[],
44+
evaluations=[],
45+
pickle_file_path=pickle_file_path,
46+
)
47+
48+
49+
def test_save_and_load_study_json(tmp_path):
50+
tmp_file = tmp_path / "out.json"
51+
52+
search_space = ps.ParameterSpace()
53+
search_space.add(ps.IntegerParameter("p", (-10.0, 10.0)))
54+
objectives = [bbo.Objective("loss", False), bbo.Objective("score", True)]
55+
evaluations = [
56+
bbo.Evaluation(configuration={"p": 1}, objectives={"loss": 1.0, "score": 3.0}),
57+
bbo.Evaluation(configuration={"p": 2}, objectives={"loss": 0.1, "score": 2.0}),
58+
bbo.Evaluation(configuration={"p": 3}, objectives={"loss": 0.0, "score": 1.0}),
59+
]
60+
bbo.io.save_study_as_json(search_space, objectives, evaluations, tmp_file)
61+
62+
# Check that default overwrite=False causes ValueError on existing file
63+
with pytest.raises(IOError):
64+
bbo.io.save_study_as_json(search_space, objectives, evaluations, tmp_file)
65+
66+
loaded_study = bbo.io.load_study_from_json(tmp_file)
67+
68+
assert loaded_study[1] == objectives
69+
assert loaded_study[2] == evaluations
70+
for _ in range(128):
71+
assert search_space.sample() == loaded_study[0].sample()
72+
73+
74+
def test_save_and_load_study_json_fails_on_missing_output_directory():
75+
json_file_path = "/this/directory/does/not/exist/jsons/out.json"
76+
with pytest.raises(IOError, match=json_file_path):
77+
bbo.io.save_study_as_json(
78+
search_space=ps.ParameterSpace(),
79+
objectives=[],
80+
evaluations=[],
81+
json_file_path=json_file_path,
82+
)
83+
84+
85+
def test_save_and_load_study_json_fails_with_complex_type_in_evaluation(tmp_path):
86+
tmp_file = tmp_path / "out.json"
87+
88+
search_space = ps.ParameterSpace()
89+
objectives = []
90+
evaluations = [
91+
bbo.Evaluation(
92+
configuration={},
93+
objectives={},
94+
user_info={"complex typed value": ps.ParameterSpace()},
95+
),
96+
]
97+
98+
with pytest.raises(TypeError):
99+
bbo.io.save_study_as_json(search_space, objectives, evaluations, tmp_file)

0 commit comments

Comments
 (0)