Skip to content

Commit 44d9c01

Browse files
0.29.23
Designs
1 parent eee94f9 commit 44d9c01

8 files changed

Lines changed: 297 additions & 3 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
77

88
[project]
99
name = "spotpython"
10-
version = "0.29.22"
10+
version = "0.29.23"
1111
authors = [
1212
{ name="T. Bartz-Beielstein", email="tbb@bartzundbartz.de" }
1313
]

src/spotpython/design/clustered.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import numpy as np
2+
from spotpython.design.designs import Designs
3+
from typing import Optional
4+
from sklearn.datasets import make_blobs
5+
6+
7+
class Clustered(Designs):
8+
"""
9+
Super class for clustered designs.
10+
11+
Attributes:
12+
k (int): The number of factors.
13+
seed (int): The seed for the random number generator.
14+
"""
15+
16+
def __init__(self, k: int = 2, seed: int = 123) -> None:
17+
"""
18+
Initializes a clustered design object.
19+
20+
Args:
21+
k (int): The number of factors. Defaults to 2.
22+
seed (int): The seed for the random number generator. Defaults to 123.
23+
"""
24+
super().__init__(k, seed)
25+
self.k = k
26+
self.seed = seed
27+
28+
def generate_clustered_design(self, n_points: int, n_clusters: int, seed: Optional[int] = None) -> np.ndarray:
29+
"""Generates a clustered design.
30+
31+
Args:
32+
n_points (int): The number of points to generate.
33+
n_clusters (int): The number of clusters.
34+
seed (Optional[int]): Optional seed for reproducibility.
35+
36+
Returns:
37+
numpy.ndarray: A 2D array of shape (n_points, n_dim) with clustered points.
38+
39+
Examples:
40+
>>> from spotpython.design.clustered import Clustered
41+
>>> clustered_design = Clustered(k=3)
42+
>>> clustered_design.generate_clustered_design(n_points=100, n_clusters=5, seed=42)
43+
array([[0.12, 0.34, 0.56],
44+
[0.23, 0.45, 0.67],
45+
...])
46+
"""
47+
X, _ = make_blobs(n_samples=n_points, n_features=self.k, centers=n_clusters, cluster_std=0.05, random_state=seed, center_box=(0.1, 0.9))
48+
X_min = X.min(axis=0)
49+
X_max = X.max(axis=0)
50+
if np.any(X_min < 0) or np.any(X_max > 1):
51+
X = (X - X_min) / (X_max - X_min + 1e-6)
52+
return X

src/spotpython/design/factorial.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import numpy as np
22
from numpy import mgrid
3-
from .designs import designs
3+
from spotpython.design.designs import Designs
44

55

6-
class factorial(designs):
6+
class factorial(Designs):
77
"""
88
Super class for factorial designs.
99

src/spotpython/design/grid.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import numpy as np
2+
from spotpython.design.designs import Designs
3+
4+
5+
class Grid(Designs):
6+
"""
7+
Super class for grid designs.
8+
9+
Attributes:
10+
k (int): The number of factors (dimension).
11+
seed (int): The seed for the random number generator.
12+
"""
13+
14+
def __init__(self, k: int = 2, seed: int = 123) -> None:
15+
"""
16+
Initializes a grid design object.
17+
18+
Args:
19+
k (int): The number of factors. Defaults to 2.
20+
seed (int): The seed for the random number generator. Defaults to 123.
21+
"""
22+
super().__init__(k, seed)
23+
self.k = k
24+
self.seed = seed
25+
26+
def generate_grid_design(self, points_per_dim: int) -> np.ndarray:
27+
"""Generates a regular grid design.
28+
29+
Args:
30+
points_per_dim (int): The number of points per dimension.
31+
32+
Returns:
33+
numpy.ndarray: A 2D array of shape (points_per_dim^n_dim, n_dim) with grid points.
34+
35+
Examples:
36+
>>> from spotpython.design.grid import Grid
37+
>>> grid_design = Grid(k=2)
38+
>>> grid_points = grid_design.generate_grid_design(points_per_dim=5)
39+
>>> print(grid_points)
40+
[[0. 0. ]
41+
[0. 0.25]
42+
[0. 0.5 ]
43+
...
44+
[1. 1. ]]
45+
46+
"""
47+
if self.k != 2:
48+
raise ValueError("Grid design currently implemented for 2D only for simplicity.")
49+
ticks = np.linspace(0, 1, points_per_dim, endpoint=True)
50+
x, y = np.meshgrid(ticks, ticks)
51+
return np.vstack([x.ravel(), y.ravel()]).T

src/spotpython/design/poor.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import numpy as np
2+
from spotpython.design.designs import Designs
3+
4+
5+
class Poor(Designs):
6+
"""
7+
Super class for poorly projected (collinear) designs.
8+
9+
Attributes:
10+
k (int): The number of factors.
11+
seed (int): The seed for the random number generator.
12+
"""
13+
14+
def __init__(self, k: int = 2, seed: int = 123) -> None:
15+
"""
16+
Initializes a Poor design object.
17+
18+
Args:
19+
k (int): The number of factors. Defaults to 2.
20+
seed (int): The seed for the random number generator. Defaults to 123.
21+
"""
22+
super().__init__(k, seed)
23+
self.k = k
24+
self.seed = seed
25+
26+
def generate_collinear_design(self, n_points: int) -> np.ndarray:
27+
"""Generates a collinear design (poorly projected).
28+
29+
Args:
30+
n_points (int): The number of points to generate.
31+
32+
Returns:
33+
numpy.ndarray: A 2D array of shape (n_points, n_dim) with collinear points.
34+
35+
Examples:
36+
>>> from spotpython.design.poor import Poor
37+
>>> poor_design = Poor(k=2)
38+
>>> collinear_points = poor_design.generate_collinear_design(n_points=10)
39+
>>> print(collinear_points)
40+
[[0.1 0.5 ]
41+
[0.2 0.5 ]
42+
[0.3 0.5 ]
43+
...
44+
[0.9 0.5 ]]
45+
46+
"""
47+
if self.k != 2:
48+
raise ValueError("Collinear design currently implemented for 2D only.")
49+
x_coords = np.linspace(0.1, 0.9, n_points)
50+
y_coords = np.full_like(x_coords, 0.5) # All points on y=0.5 line
51+
# y_coords = 0.2 * x_coords + 0.3 # Or a sloped line
52+
return np.vstack([x_coords, y_coords]).T

src/spotpython/design/random.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import numpy as np
2+
from spotpython.design.designs import Designs
3+
4+
5+
class Random(Designs):
6+
"""
7+
Super class for random designs.
8+
9+
Attributes:
10+
k (int): The number of factors.
11+
seed (int): The seed for the random number generator.
12+
"""
13+
14+
def __init__(self, k: int = 2, seed: int = 123) -> None:
15+
"""
16+
Initializes a random design object.
17+
18+
Args:
19+
k (int): The number of factors. Defaults to 2.
20+
seed (int): The seed for the random number generator. Defaults to 123.
21+
"""
22+
super().__init__(k, seed)
23+
self.k = k
24+
self.seed = seed
25+
26+
def uniform(self, n_points: int, seed: int = None) -> np.ndarray:
27+
"""
28+
Generates a random design using uniform distribution.
29+
30+
Args:
31+
n_points (int): The number of points to generate.
32+
seed (int, optional): The seed for the random number generator. If None, uses the instance's seed.
33+
34+
Returns:
35+
numpy.ndarray: A 2D array of shape (n_points, k) with random values in [0, 1).
36+
37+
Examples:
38+
>>> from spotpython.design.random import Random
39+
>>> random_design = Random(k=3)
40+
>>> random_design.uniform(n_points=5)
41+
array([[0.123, 0.456, 0.789],
42+
[0.234, 0.567, 0.890],
43+
[0.345, 0.678, 0.901],
44+
[0.456, 0.789, 0.012],
45+
[0.567, 0.890, 0.123]])
46+
47+
"""
48+
if seed is not None:
49+
seed = self.seed
50+
rng = np.random.default_rng(seed)
51+
return rng.random((n_points, self.k))

src/spotpython/design/sobol.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import numpy as np
2+
from spotpython.design.designs import Designs
3+
from scipy.stats import qmc
4+
from typing import Optional
5+
6+
7+
class Sobol(Designs):
8+
"""
9+
Super class for sobol designs.
10+
11+
Attributes:
12+
k (int): The number of factors.
13+
seed (int): The seed for the random number generator.
14+
"""
15+
16+
def __init__(self, k: int = 2, seed: int = 123) -> None:
17+
"""
18+
Initializes a sobol design object.
19+
20+
Args:
21+
k (int): The number of factors (dimension). Defaults to 2.
22+
seed (int): The seed for the random number generator. Defaults to 123.
23+
"""
24+
super().__init__(k, seed)
25+
self.k = k
26+
self.seed = seed
27+
28+
def generate_sobol_design(self, n_points: int, seed: int = None) -> np.ndarray:
29+
"""Generates a Sobol sequence design
30+
31+
Args:
32+
n_points (int):
33+
The number of points to generate in the Sobol sequence.
34+
seed (Optional[int]):
35+
The seed for the random number generator.
36+
If None, uses the instance's seed.
37+
38+
Returns:
39+
np.ndarray: An array of shape (n_points, n_dim) containing the generated Sobol sequence points.
40+
41+
Notes:
42+
- The Sobol sequence is generated with a length that is a power of 2. The function generates at least n_points and returns the first n_points.
43+
- For n_points not being a power of 2, extra points are generated and truncated.
44+
- Scrambling is enabled for improved uniformity.
45+
46+
Examples:
47+
>>> from spotpython.design.sobol import Sobol
48+
>>> sobol_design = Sobol(k=3, seed=42)
49+
>>> sobol_points = sobol_design.generate_sobol_design(n_points=10)
50+
>>> print(sobol_points.shape)
51+
(10, 3)
52+
"""
53+
if seed is not None:
54+
self.seed = seed
55+
sampler = qmc.Sobol(d=self.k, scramble=True, seed=seed)
56+
m = int(np.ceil(np.log2(n_points)))
57+
return sampler.random_base2(m=m)[:n_points, :]

src/spotpython/design/spacefilling.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from spotpython.utils.transform import scale
44
from typing import Optional, Union
55
from spotpython.design.designs import Designs
6+
from scipy.stats import qmc
67

78

89
class SpaceFilling(Designs):
@@ -94,3 +95,33 @@ def scipy_lhd(
9495
sample = self.sampler.random(n=n)
9596
des = scale(sample, lower, upper)
9697
return np.repeat(des, repeats, axis=0)
98+
99+
def generate_qms_lhs_design(self, n_points: int, seed: Optional[int] = None) -> np.ndarray:
100+
"""Generates a Latin Hypercube Sampling design using the `scipy.stats.qmc` module.
101+
Generates a Latin Hypercube Sampling (LHS) design with the specified number of points
102+
and dimensions.
103+
104+
Args:
105+
n_points (int): The number of points to generate.
106+
seed (Optional[int]):
107+
Seed for the random number generator to ensure reproducibility.
108+
Defaults to None. If None, uses the seed specified during initialization.
109+
110+
Returns:
111+
np.ndarray: An array of shape (n_points, n_dim) containing the generated Latin Hypercube Sampling points.
112+
113+
Notes:
114+
- The Latin Hypercube Sampling is generated with a specified number of points and dimensions.
115+
- The points are uniformly distributed across the unit hypercube [0, 1]^n_dim.
116+
117+
Examples:
118+
>>> from spotpython.design.spacefilling import SpaceFilling
119+
>>> lhs_design = SpaceFilling(k=3, seed=42)
120+
>>> lhs_points = lhs_design.generate_qms_lhs_design(n_points=10)
121+
>>> print(lhs_points.shape)
122+
(10, 3)
123+
"""
124+
if seed is None:
125+
seed = self.seed
126+
sampler = qmc.LatinHypercube(d=self.k, seed=seed)
127+
return sampler.random(n=n_points)

0 commit comments

Comments
 (0)