sgptools 1.2.0__py3-none-any.whl → 2.0.0__py3-none-any.whl
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.
- sgptools/__init__.py +3 -4
- sgptools/core/__init__.py +1 -0
- sgptools/{models/core → core}/augmented_gpr.py +11 -17
- sgptools/{models/core → core}/augmented_sgpr.py +27 -34
- sgptools/core/osgpr.py +417 -0
- sgptools/core/transformations.py +699 -0
- sgptools/kernels/__init__.py +0 -8
- sgptools/kernels/attentive_kernel.py +214 -69
- sgptools/kernels/neural_kernel.py +268 -92
- sgptools/kernels/neural_network.py +127 -28
- sgptools/methods.py +1047 -0
- sgptools/objectives.py +275 -0
- sgptools/utils/__init__.py +0 -9
- sgptools/utils/data.py +452 -149
- sgptools/utils/gpflow.py +335 -174
- sgptools/utils/metrics.py +375 -102
- sgptools/utils/misc.py +145 -111
- sgptools/utils/tsp.py +224 -84
- sgptools-2.0.0.dist-info/METADATA +216 -0
- sgptools-2.0.0.dist-info/RECORD +23 -0
- {sgptools-1.2.0.dist-info → sgptools-2.0.0.dist-info}/WHEEL +1 -1
- sgptools/models/__init__.py +0 -10
- sgptools/models/bo.py +0 -118
- sgptools/models/cma_es.py +0 -121
- sgptools/models/continuous_sgp.py +0 -68
- sgptools/models/core/__init__.py +0 -9
- sgptools/models/core/osgpr.py +0 -291
- sgptools/models/core/transformations.py +0 -434
- sgptools/models/greedy_mi.py +0 -115
- sgptools/models/greedy_sgp.py +0 -97
- sgptools-1.2.0.dist-info/METADATA +0 -39
- sgptools-1.2.0.dist-info/RECORD +0 -27
- {sgptools-1.2.0.dist-info → sgptools-2.0.0.dist-info/licenses}/LICENSE.txt +0 -0
- {sgptools-1.2.0.dist-info → sgptools-2.0.0.dist-info}/top_level.txt +0 -0
sgptools/utils/misc.py
CHANGED
@@ -1,162 +1,196 @@
|
|
1
|
-
from .metrics import get_distance
|
2
|
-
|
3
1
|
from scipy.optimize import linear_sum_assignment
|
4
2
|
from sklearn.metrics import pairwise_distances
|
5
3
|
from scipy.cluster.vq import kmeans2
|
6
4
|
from shapely import geometry
|
7
5
|
import geopandas as gpd
|
8
6
|
|
9
|
-
import matplotlib.pyplot as plt
|
10
7
|
import numpy as np
|
8
|
+
from typing import Tuple, Optional, Union
|
11
9
|
|
12
10
|
|
13
|
-
def get_inducing_pts(data
|
14
|
-
|
15
|
-
|
11
|
+
def get_inducing_pts(data: np.ndarray,
|
12
|
+
num_inducing: int,
|
13
|
+
orientation: bool = False,
|
14
|
+
random: bool = False) -> np.ndarray:
|
15
|
+
"""
|
16
|
+
Selects a subset of data points to be used as inducing points.
|
17
|
+
By default, it uses k-means clustering to select representative points.
|
18
|
+
Alternatively, it can select points randomly.
|
19
|
+
If `orientation` is True, an additional dimension representing a rotation angle
|
20
|
+
is appended to each inducing point.
|
16
21
|
|
17
22
|
Args:
|
18
|
-
data (ndarray): (n,
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
23
|
+
data (np.ndarray): (n, d_in); Input data points from which to select inducing points.
|
24
|
+
`n` is the number of data points, `d_in` is the input dimensionality.
|
25
|
+
num_inducing (int): The desired number of inducing points to select.
|
26
|
+
orientation (bool): If True, a random orientation angle (in radians, from 0 to 2*pi)
|
27
|
+
is added as an additional dimension to each inducing point.
|
28
|
+
Defaults to False.
|
29
|
+
random (bool): If True, inducing points are selected randomly from `data`.
|
30
|
+
If False, k-means clustering (`kmeans2`) is used for selection.
|
31
|
+
Defaults to False.
|
24
32
|
|
25
33
|
Returns:
|
26
|
-
|
27
|
-
|
28
|
-
|
34
|
+
np.ndarray: (m, d_out); Inducing points. `m` is `num_inducing`.
|
35
|
+
`d_out` is `d_in` if `orientation` is False, or `d_in + 1` if `orientation` is True.
|
36
|
+
If `orientation` is True, the last dimension contains angles in radians.
|
37
|
+
|
38
|
+
Usage:
|
39
|
+
```python
|
40
|
+
import numpy as np
|
41
|
+
from sgptools.utils.misc import get_inducing_pts
|
42
|
+
|
43
|
+
# Example data (1000 2D points)
|
44
|
+
data_points = np.random.rand(1000, 2) * 100
|
45
|
+
|
46
|
+
# 1. Select 50 inducing points using k-means (default)
|
47
|
+
inducing_points_kmeans = get_inducing_pts(data_points, 50)
|
48
|
+
|
49
|
+
# 2. Select 20 inducing points randomly with orientation
|
50
|
+
inducing_points_random_oriented = get_inducing_pts(data_points, 20, orientation=True, random=True)
|
51
|
+
```
|
29
52
|
"""
|
30
53
|
if random:
|
31
|
-
|
54
|
+
# Randomly select `num_inducing` indices from the data
|
55
|
+
idx = np.random.choice(len(data), size=num_inducing, replace=False)
|
32
56
|
Xu = data[idx]
|
33
57
|
else:
|
58
|
+
# Use k-means clustering to find `num_inducing` cluster centers
|
59
|
+
# `minit="points"` initializes centroids by picking random data points
|
34
60
|
Xu = kmeans2(data, num_inducing, minit="points")[0]
|
61
|
+
|
35
62
|
if orientation:
|
63
|
+
# Generate random angles between 0 and 2*pi (radians)
|
36
64
|
thetas = np.random.uniform(0, 2 * np.pi, size=(Xu.shape[0], 1))
|
65
|
+
# Concatenate the points with their corresponding angles
|
37
66
|
Xu = np.concatenate([Xu, thetas], axis=1)
|
67
|
+
|
38
68
|
return Xu
|
39
69
|
|
40
|
-
|
41
|
-
|
70
|
+
|
71
|
+
def cont2disc(
|
72
|
+
Xu: np.ndarray,
|
73
|
+
candidates: np.ndarray,
|
74
|
+
candidate_labels: Optional[np.ndarray] = None
|
75
|
+
) -> Union[np.ndarray, Tuple[np.ndarray, np.ndarray]]:
|
76
|
+
"""
|
77
|
+
Maps continuous space locations (`Xu`) to the closest points in a discrete
|
78
|
+
set of candidate locations (`candidates`) using a Hungarian algorithm
|
79
|
+
(linear sum assignment) for optimal matching. This ensures each `Xu` point
|
80
|
+
is matched to a unique candidate.
|
42
81
|
|
43
82
|
Args:
|
44
|
-
Xu (ndarray): (m,
|
45
|
-
|
46
|
-
|
83
|
+
Xu (np.ndarray): (m, d); Continuous space points (e.g., optimized sensor locations).
|
84
|
+
`m` is the number of points, `d` is the dimensionality.
|
85
|
+
candidates (np.ndarray): (n, d); Discrete set of candidate locations.
|
86
|
+
`n` is the number of candidates, `d` is the dimensionality.
|
87
|
+
candidate_labels (Optional[np.ndarray]): (n, 1); Optional labels corresponding to
|
88
|
+
the discrete set of candidate locations.
|
89
|
+
If provided, the matched labels are also returned.
|
90
|
+
Defaults to None.
|
47
91
|
|
48
92
|
Returns:
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
93
|
+
Union[np.ndarray, Tuple[np.ndarray, np.ndarray]]:
|
94
|
+
- If `candidate_labels` is None:
|
95
|
+
np.ndarray: (m, d); Discrete space points' locations (`Xu_X`),
|
96
|
+
where each point in `Xu` is mapped to its closest
|
97
|
+
unique point in `candidates`.
|
98
|
+
- If `candidate_labels` is provided:
|
99
|
+
Tuple[np.ndarray, np.ndarray]: (`Xu_X`, `Xu_y`).
|
100
|
+
`Xu_X` (np.ndarray): (m, d); The matched discrete locations.
|
101
|
+
`Xu_y` (np.ndarray): (m, 1); The labels corresponding to `Xu_X`.
|
102
|
+
|
103
|
+
Usage:
|
104
|
+
```python
|
105
|
+
import numpy as np
|
106
|
+
from sgptools.utils.misc import cont2disc
|
107
|
+
|
108
|
+
# Example continuous points
|
109
|
+
continuous_points = np.array([[0.1, 0.1], [0.9, 0.9], [0.5, 0.5]])
|
110
|
+
# Example discrete candidates
|
111
|
+
discrete_candidates = np.array([[0.0, 0.0], [1.0, 1.0], [0.4, 0.6]])
|
112
|
+
# Example candidate labels (optional)
|
113
|
+
discrete_labels = np.array([[10.0], [20.0], [15.0]])
|
114
|
+
|
115
|
+
# 1. Map without labels
|
116
|
+
mapped_points = cont2disc(continuous_points, discrete_candidates)
|
117
|
+
|
118
|
+
# 2. Map with labels
|
119
|
+
mapped_points_X, mapped_points_y = cont2disc(continuous_points, discrete_candidates, discrete_labels)
|
120
|
+
```
|
53
121
|
"""
|
54
|
-
# Sanity check to
|
55
|
-
if len(candidates)==0 or len(Xu)==0:
|
122
|
+
# Sanity check to handle empty inputs gracefully
|
123
|
+
if len(candidates) == 0 or len(Xu) == 0:
|
56
124
|
if candidate_labels is not None:
|
57
|
-
return [],
|
125
|
+
return np.empty((0, Xu.shape[1])), np.empty((0, 1))
|
58
126
|
else:
|
59
|
-
return []
|
60
|
-
|
127
|
+
return np.empty((0, Xu.shape[1]))
|
128
|
+
|
129
|
+
# Compute pairwise Euclidean distances between candidates and Xu
|
61
130
|
dists = pairwise_distances(candidates, Y=Xu, metric='euclidean')
|
62
|
-
|
131
|
+
|
132
|
+
# Use the Hungarian algorithm (linear_sum_assignment) to find the optimal
|
133
|
+
# assignment of rows (candidates) to columns (Xu points) that minimizes
|
134
|
+
# the total cost (distances). `row_ind` gives the indices of the rows
|
135
|
+
# (candidates) chosen, `col_ind` gives the corresponding indices of `Xu`.
|
136
|
+
row_ind, col_ind = linear_sum_assignment(dists)
|
137
|
+
|
138
|
+
# Select the candidate locations that were matched to Xu points
|
63
139
|
Xu_X = candidates[row_ind].copy()
|
140
|
+
|
64
141
|
if candidate_labels is not None:
|
142
|
+
# If labels are provided, select the corresponding labels as well
|
65
143
|
Xu_y = candidate_labels[row_ind].copy()
|
66
144
|
return Xu_X, Xu_y
|
67
145
|
else:
|
68
146
|
return Xu_X
|
69
147
|
|
70
|
-
def plot_paths(paths, candidates=None, title=None):
|
71
|
-
"""Function to plot the IPP solution paths
|
72
148
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
title (str): Title of the plot
|
149
|
+
def polygon2candidates(vertices: np.ndarray,
|
150
|
+
num_samples: int = 5000,
|
151
|
+
random_seed: Optional[int] = None) -> np.ndarray:
|
77
152
|
"""
|
78
|
-
|
79
|
-
|
80
|
-
plt.plot(path[:, 0], path[:, 1],
|
81
|
-
c='r', label='Path', zorder=1, marker='o')
|
82
|
-
plt.scatter(path[0, 0], path[0, 1],
|
83
|
-
c='g', label='Start', zorder=2, marker='o')
|
84
|
-
if candidates is not None:
|
85
|
-
plt.scatter(candidates[:, 0], candidates[:, 1],
|
86
|
-
c='k', s=1, label='Unlabeled Train-Set Points', zorder=0)
|
87
|
-
if i==0:
|
88
|
-
plt.legend(bbox_to_anchor=(1.0, 1.02))
|
89
|
-
if title is not None:
|
90
|
-
plt.title(title)
|
91
|
-
plt.gca().set_aspect('equal')
|
92
|
-
plt.xlabel('X')
|
93
|
-
plt.ylabel('Y')
|
94
|
-
|
95
|
-
def interpolate_path(waypoints, sampling_rate=0.05):
|
96
|
-
"""Interpolate additional points between the given waypoints to simulate continuous sensing robots
|
153
|
+
Samples a specified number of candidate points randomly within a polygon defined by its vertices.
|
154
|
+
This function leverages `geopandas` for geometric operations.
|
97
155
|
|
98
156
|
Args:
|
99
|
-
|
100
|
-
|
157
|
+
vertices (np.ndarray): (v, 2); A NumPy array where each row represents the (x, y)
|
158
|
+
coordinates of a vertex defining the polygon. `v` is the
|
159
|
+
number of vertices. The polygon is closed automatically if
|
160
|
+
the first and last vertices are not identical.
|
161
|
+
num_samples (int): The desired number of candidate points to sample within the polygon.
|
162
|
+
Defaults to 5000.
|
163
|
+
random_seed (Optional[int]): Seed for reproducibility of the random point sampling.
|
164
|
+
Defaults to None.
|
101
165
|
|
102
166
|
Returns:
|
103
|
-
|
104
|
-
|
105
|
-
interpolated_path = []
|
106
|
-
for i in range(2, len(waypoints)+1):
|
107
|
-
dist = get_distance(waypoints[i-2:i])
|
108
|
-
num_samples = int(dist / sampling_rate)
|
109
|
-
points = np.linspace(waypoints[i-1], waypoints[i-2], num_samples)
|
110
|
-
interpolated_path.extend(points)
|
111
|
-
return np.array(interpolated_path)
|
112
|
-
|
113
|
-
def _reoder_path(path, waypoints):
|
114
|
-
"""Reorder the waypoints to match the order of the points in the path.
|
115
|
-
The waypoints are mathched to the closest points in the path. Used by project_waypoints.
|
167
|
+
np.ndarray: (n, 2); A NumPy array where each row represents the (x, y) coordinates
|
168
|
+
of a sampled candidate sensor placement location. `n` is `num_samples`.
|
116
169
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
Returns:
|
122
|
-
waypoints (n, d): Reordered waypoints of the robot's path
|
123
|
-
"""
|
124
|
-
dists = pairwise_distances(path, Y=waypoints, metric='euclidean')
|
125
|
-
_, col_ind = linear_sum_assignment(dists)
|
126
|
-
Xu = waypoints[col_ind].copy()
|
127
|
-
return Xu
|
128
|
-
|
129
|
-
def project_waypoints(waypoints, candidates):
|
130
|
-
"""Project the waypoints back to the candidate set while retaining the
|
131
|
-
waypoint visitation order.
|
170
|
+
Usage:
|
171
|
+
```python
|
172
|
+
import numpy as np
|
173
|
+
# from sgptools.utils.misc import polygon2candidates
|
132
174
|
|
133
|
-
|
134
|
-
|
135
|
-
candidates (ndarray): (n, 2); Discrete set of candidate locations
|
175
|
+
# Define vertices for a square polygon
|
176
|
+
square_vertices = np.array([[0, 0], [10, 0], [10, 10], [0, 10]])
|
136
177
|
|
137
|
-
|
138
|
-
|
178
|
+
# Sample 100 candidate points within the square
|
179
|
+
sampled_candidates = polygon2candidates(square_vertices, num_samples=100, random_seed=42)
|
180
|
+
```
|
139
181
|
"""
|
140
|
-
|
141
|
-
|
142
|
-
return waypoints_valid
|
182
|
+
# Create a shapely Polygon object from the provided vertices
|
183
|
+
poly = geometry.Polygon(vertices)
|
143
184
|
|
144
|
-
|
145
|
-
|
146
|
-
random_seed=2024):
|
147
|
-
"""Sample unlabeled candidates within a polygon
|
185
|
+
# Create a GeoSeries containing the polygon, which enables sampling points
|
186
|
+
sampler = gpd.GeoSeries([poly])
|
148
187
|
|
149
|
-
|
150
|
-
|
151
|
-
num_samples
|
152
|
-
random_seed
|
188
|
+
# Sample random points within the polygon
|
189
|
+
candidates_geoseries = sampler.sample_points(
|
190
|
+
size=num_samples,
|
191
|
+
rng=random_seed) # `rng` is for random number generator seed
|
153
192
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
sampler = gpd.GeoSeries([poly])
|
159
|
-
candidates = sampler.sample_points(size=num_samples,
|
160
|
-
rng=random_seed)
|
161
|
-
candidates = candidates.get_coordinates().to_numpy()
|
162
|
-
return candidates
|
193
|
+
# Extract coordinates from the GeoSeries of points and convert to a NumPy array
|
194
|
+
candidates_array = candidates_geoseries.get_coordinates().to_numpy()
|
195
|
+
|
196
|
+
return candidates_array
|