Skip to content

Commit 377474f

Browse files
authored
Use scipy for model free designs (#483)
1 parent 5c27edc commit 377474f

File tree

6 files changed

+41
-41
lines changed

6 files changed

+41
-41
lines changed

emukit/core/initial_designs/latin_design.py

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,7 @@
66

77

88
import numpy as np
9-
10-
try:
11-
import pyDOE
12-
except ImportError:
13-
raise ImportError("pyDOE needs to be installed in order to use latin design")
9+
from scipy.stats import qmc
1410

1511
from .. import ParameterSpace
1612
from .base import InitialDesignBase
@@ -20,7 +16,8 @@ class LatinDesign(InitialDesignBase):
2016
"""
2117
Latin hypercube experiment design.
2218
23-
Based on pyDOE implementation. For further reference see https://pythonhosted.org/pyDOE/randomized.html#latin-hypercube
19+
Based on scipy implementation. For further reference see
20+
https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.qmc.LatinHypercube.html
2421
"""
2522

2623
def __init__(self, parameter_space: ParameterSpace) -> None:
@@ -37,15 +34,14 @@ def get_samples(self, point_count: int) -> np.ndarray:
3734
:return: A numpy array of generated samples, shape (point_count x space_dim)
3835
"""
3936
bounds = self.parameter_space.get_bounds()
40-
X_design_aux = pyDOE.lhs(len(bounds), point_count, criterion="center")
41-
ones = np.ones((X_design_aux.shape[0], 1))
42-
43-
lower_bound = np.asarray(bounds)[:, 0].reshape(1, len(bounds))
44-
upper_bound = np.asarray(bounds)[:, 1].reshape(1, len(bounds))
45-
diff = upper_bound - lower_bound
37+
d = len(bounds)
38+
lower_bounds = [x[0] for x in bounds]
39+
upper_bounds = [x[1] for x in bounds]
4640

47-
X_design = np.dot(ones, lower_bound) + X_design_aux * np.dot(ones, diff)
41+
sampler = qmc.LatinHypercube(d)
42+
samples = sampler.random(n=point_count)
43+
samples = qmc.scale(samples, lower_bounds, upper_bounds)
4844

49-
samples = self.parameter_space.round(X_design)
45+
X_design = self.parameter_space.round(samples)
5046

51-
return samples
47+
return X_design
Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,12 @@
1-
# Copyright 2020-2024 The Emukit Authors. All Rights Reserved.
1+
# Copyright 2020-2026 The Emukit Authors. All Rights Reserved.
22
# SPDX-License-Identifier: Apache-2.0
33

44
# Copyright 2018-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
55
# SPDX-License-Identifier: Apache-2.0
66

77

8-
# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
9-
# SPDX-License-Identifier: Apache-2.0
10-
11-
128
import numpy as np
13-
14-
try:
15-
from sobol_seq import i4_sobol_generate
16-
except ImportError:
17-
raise ImportError("sobol_seq needs to be installed in order to use sobol design")
9+
from scipy.stats import qmc
1810

1911
from .. import ParameterSpace
2012
from .base import InitialDesignBase
@@ -23,7 +15,8 @@
2315
class SobolDesign(InitialDesignBase):
2416
"""
2517
Sobol experiment design.
26-
Based on sobol_seq implementation. For further reference see https://github.com/naught101/sobol_seq
18+
Based on scipy implementation. For further reference see
19+
https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.qmc.Sobol.html
2720
"""
2821

2922
def __init__(self, parameter_space: ParameterSpace) -> None:
@@ -40,12 +33,14 @@ def get_samples(self, point_count: int) -> np.ndarray:
4033
:return: A numpy array of generated samples, shape (point_count x space_dim)
4134
"""
4235
bounds = self.parameter_space.get_bounds()
43-
lower_bound = np.asarray(bounds)[:, 0].reshape(1, len(bounds))
44-
upper_bound = np.asarray(bounds)[:, 1].reshape(1, len(bounds))
45-
diff = upper_bound - lower_bound
36+
d = len(bounds)
37+
lower_bounds = [x[0] for x in bounds]
38+
upper_bounds = [x[1] for x in bounds]
4639

47-
X_design = np.dot(i4_sobol_generate(len(bounds), point_count), np.diag(diff[0, :])) + lower_bound
40+
sampler = qmc.Sobol(d)
41+
samples = sampler.random(n=point_count)
42+
samples = qmc.scale(samples, lower_bounds, upper_bounds)
4843

49-
samples = self.parameter_space.round(X_design)
44+
X_design = self.parameter_space.round(samples)
5045

51-
return samples
46+
return X_design

pyproject.toml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,6 @@ dependencies = [
3131
"scipy",
3232
"matplotlib>=3.9",
3333
"emcee>=2.2.1",
34-
"pydoe<0.9.6",
35-
"sobol_seq>=0.1.2",
3634
]
3735

3836
[project.optional-dependencies]

requirements/integration_test_requirements.txt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
scikit-learn
33
# For BNN model
44
pybnn==0.0.5
5-
# For Latin design
6-
PyDOE>=0.3.0
75
# For notebook tests
86
jupyter==1.0.0
97
pandas>=1.0.5

requirements/requirements.txt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,3 @@ matplotlib>=3.9
77
# GPy>=1.13.0
88
emcee>=2.2.1
99
scipy
10-
PyDOE>=0.3.0
11-
sobol_seq>=0.1.2

tests/emukit/core/test_model_free_designs.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
# Copyright 2020-2024 The Emukit Authors. All Rights Reserved.
1+
# Copyright 2020-2026 The Emukit Authors. All Rights Reserved.
22
# SPDX-License-Identifier: Apache-2.0
33

44
# Copyright 2018-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
55
# SPDX-License-Identifier: Apache-2.0
66

7+
import numpy as np
78

89
from emukit.core import CategoricalParameter, ContinuousParameter, DiscreteParameter, ParameterSpace
910
from emukit.core.initial_designs import RandomDesign
@@ -25,8 +26,22 @@ def test_design_returns_correct_number_of_points():
2526
points = design.get_samples(points_count)
2627

2728
assert points_count == len(points)
28-
columns_count = 1
29-
assert all([len(p) == columns_count for p in points])
29+
assert all([len(p) == 1 for p in points])
30+
31+
32+
def test_design_returns_points_within_bounds():
33+
p1 = ContinuousParameter("p1", 0.01, 0.05)
34+
p2 = ContinuousParameter("p2", -100.0, -90.0)
35+
space = ParameterSpace([p1, p2])
36+
points_count = 5
37+
38+
designs = create_model_free_designs(space)
39+
for design in designs:
40+
points = design.get_samples(points_count)
41+
42+
for i, p in enumerate(space.parameters):
43+
assert np.all(p.min <= points[:, i])
44+
assert np.all(points[:, i] <= p.max)
3045

3146

3247
def test_design_with_mixed_domain(encoding):

0 commit comments

Comments
 (0)