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/tsp.py
CHANGED
@@ -12,6 +12,8 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
+
from typing import List, Optional, Tuple
|
16
|
+
|
15
17
|
from ortools.constraint_solver import routing_enums_pb2
|
16
18
|
from ortools.constraint_solver import pywrapcp
|
17
19
|
from sklearn.metrics import pairwise_distances
|
@@ -19,38 +21,44 @@ from shapely.geometry import LineString
|
|
19
21
|
import numpy as np
|
20
22
|
|
21
23
|
|
22
|
-
def run_tsp(
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
24
|
+
def run_tsp(
|
25
|
+
nodes: np.ndarray,
|
26
|
+
num_vehicles: int = 1,
|
27
|
+
max_dist: float = 25.0,
|
28
|
+
depth: int = 1,
|
29
|
+
resample: Optional[int] = None,
|
30
|
+
start_nodes: Optional[np.ndarray] = None,
|
31
|
+
end_nodes: Optional[np.ndarray] = None,
|
32
|
+
time_limit: int = 10,
|
33
|
+
) -> Tuple[Optional[np.ndarray], Optional[List[float]]]:
|
34
|
+
"""Method to run TSP/VRP with arbitrary start and end nodes,
|
35
|
+
and without any distance constraint.
|
36
|
+
|
33
37
|
Args:
|
34
|
-
nodes (ndarray): (# nodes, ndim); Nodes to visit
|
35
|
-
num_vehicles (int): Number of robots/vehicles
|
36
|
-
max_dist (float): Maximum distance allowed for each path when handling
|
37
|
-
depth (int): Internal parameter used to track re-try recursion depth
|
38
|
-
resample (int): Each solution path will be resampled to have
|
39
|
-
|
40
|
-
start_nodes (ndarray): (# num_vehicles, ndim);
|
41
|
-
|
42
|
-
end_nodes (ndarray): (# num_vehicles, ndim);
|
43
|
-
|
44
|
-
time_limit (int): TSP runtime time limit in seconds
|
38
|
+
nodes (np.ndarray): (# nodes, ndim); Nodes to visit.
|
39
|
+
num_vehicles (int): Number of robots/vehicles.
|
40
|
+
max_dist (float): Maximum distance allowed for each path when handling multi-robot case.
|
41
|
+
depth (int): Internal parameter used to track re-try recursion depth.
|
42
|
+
resample (Optional[int]): Each solution path will be resampled to have
|
43
|
+
`resample` number of points.
|
44
|
+
start_nodes (Optional[np.ndarray]): (# num_vehicles, ndim); Optional array of start nodes from which
|
45
|
+
to start each vehicle's solution path.
|
46
|
+
end_nodes (Optional[np.ndarray]): (# num_vehicles, ndim); Optional array of end nodes at which
|
47
|
+
to end each vehicle's solution path.
|
48
|
+
time_limit (int): TSP runtime time limit in seconds.
|
45
49
|
|
46
50
|
Returns:
|
47
|
-
|
48
|
-
|
51
|
+
Tuple[Optional[np.ndarray], Optional[List[float]]]:
|
52
|
+
- paths (np.ndarray): Solution paths if found, otherwise None.
|
53
|
+
- distances (List[float]): List of path lengths if paths are found, otherwise None.
|
49
54
|
"""
|
50
55
|
if depth > 5:
|
51
56
|
print('Warning: Max depth reached')
|
52
57
|
return None, None
|
53
|
-
|
58
|
+
|
59
|
+
# Backup original nodes
|
60
|
+
original_nodes = np.copy(nodes)
|
61
|
+
|
54
62
|
# Add the start and end nodes to the node list
|
55
63
|
if end_nodes is not None:
|
56
64
|
assert end_nodes.shape == (num_vehicles, nodes.shape[-1]), \
|
@@ -63,27 +71,27 @@ def run_tsp(nodes,
|
|
63
71
|
|
64
72
|
# Add dummy 0 location to get arbitrary start and end node sols
|
65
73
|
if start_nodes is None or end_nodes is None:
|
66
|
-
distance_mat = np.zeros((len(nodes)+1, len(nodes)+1))
|
67
|
-
distance_mat[1:, 1:] = pairwise_distances(nodes, nodes)*1e4
|
68
|
-
trim_paths = True
|
74
|
+
distance_mat = np.zeros((len(nodes) + 1, len(nodes) + 1))
|
75
|
+
distance_mat[1:, 1:] = pairwise_distances(nodes, nodes) * 1e4
|
76
|
+
trim_paths = True #shift to account for dummy node
|
69
77
|
else:
|
70
|
-
distance_mat = pairwise_distances(nodes, nodes)*1e4
|
78
|
+
distance_mat = pairwise_distances(nodes, nodes) * 1e4
|
71
79
|
trim_paths = False
|
72
80
|
distance_mat = distance_mat.astype(int)
|
73
|
-
max_dist = int(max_dist*1e4)
|
81
|
+
max_dist = int(max_dist * 1e4)
|
74
82
|
|
75
83
|
# Get start and end node indices for ortools
|
76
84
|
if start_nodes is None:
|
77
85
|
start_idx = np.zeros(num_vehicles, dtype=int)
|
78
86
|
num_start_nodes = 0
|
79
87
|
else:
|
80
|
-
start_idx = np.arange(num_vehicles)+int(trim_paths)
|
88
|
+
start_idx = np.arange(num_vehicles) + int(trim_paths)
|
81
89
|
num_start_nodes = len(start_nodes)
|
82
90
|
|
83
91
|
if end_nodes is None:
|
84
92
|
end_idx = np.zeros(num_vehicles, dtype=int)
|
85
93
|
else:
|
86
|
-
end_idx = np.arange(num_vehicles)+num_start_nodes+int(trim_paths)
|
94
|
+
end_idx = np.arange(num_vehicles) + num_start_nodes + int(trim_paths)
|
87
95
|
|
88
96
|
# used by ortools
|
89
97
|
def distance_callback(from_index, to_index):
|
@@ -92,8 +100,7 @@ def run_tsp(nodes,
|
|
92
100
|
return distance_mat[from_node][to_node]
|
93
101
|
|
94
102
|
# num_locations, num vehicles, start, end
|
95
|
-
manager = pywrapcp.RoutingIndexManager(len(distance_mat),
|
96
|
-
num_vehicles,
|
103
|
+
manager = pywrapcp.RoutingIndexManager(len(distance_mat), num_vehicles,
|
97
104
|
start_idx.tolist(),
|
98
105
|
end_idx.tolist())
|
99
106
|
routing = pywrapcp.RoutingModel(manager)
|
@@ -115,46 +122,141 @@ def run_tsp(nodes,
|
|
115
122
|
|
116
123
|
search_parameters = pywrapcp.DefaultRoutingSearchParameters()
|
117
124
|
search_parameters.first_solution_strategy = (
|
118
|
-
routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC
|
119
|
-
)
|
125
|
+
routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)
|
120
126
|
search_parameters.local_search_metaheuristic = (
|
121
|
-
routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH
|
122
|
-
)
|
127
|
+
routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)
|
123
128
|
search_parameters.time_limit.seconds = time_limit
|
124
129
|
solution = routing.SolveWithParameters(search_parameters)
|
125
|
-
|
126
|
-
paths = None
|
130
|
+
|
131
|
+
paths: Optional[List[np.ndarray]] = None
|
132
|
+
distances: Optional[List[float]] = None
|
133
|
+
|
127
134
|
if solution is not None:
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
135
|
+
paths_indices, distances_raw = _get_routes(manager, routing, solution,
|
136
|
+
num_vehicles, start_idx,
|
137
|
+
end_idx, trim_paths)
|
138
|
+
|
139
|
+
# Check for empty paths and retry with increased max_dist if necessary
|
140
|
+
for path in paths_indices:
|
132
141
|
if len(path) < 2:
|
133
|
-
print(
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
142
|
+
print(
|
143
|
+
"TSP Warning: Empty path detected, retrying with increased max_dist."
|
144
|
+
)
|
145
|
+
# Recalculate max_dist based on the current average distance
|
146
|
+
mean_dist = np.mean(
|
147
|
+
distances_raw) / 1e4 if distances_raw else max_dist
|
148
|
+
return run_tsp(
|
149
|
+
original_nodes,
|
150
|
+
num_vehicles,
|
151
|
+
mean_dist * (1.5 / depth),
|
152
|
+
depth + 1,
|
153
|
+
resample,
|
154
|
+
start_nodes,
|
155
|
+
end_nodes,
|
156
|
+
time_limit,
|
157
|
+
)
|
158
|
+
paths = [nodes[path] for path in paths_indices]
|
159
|
+
distances = [d / 1e4 for d in distances_raw]
|
138
160
|
|
139
|
-
|
140
|
-
|
161
|
+
else:
|
162
|
+
print(
|
163
|
+
"TSP Warning: No solution found, retrying with increased max_dist."
|
164
|
+
)
|
165
|
+
return run_tsp(
|
166
|
+
original_nodes,
|
167
|
+
num_vehicles,
|
168
|
+
max_dist * 1.5,
|
169
|
+
depth + 1,
|
170
|
+
resample,
|
171
|
+
start_nodes,
|
172
|
+
end_nodes,
|
173
|
+
time_limit,
|
174
|
+
)
|
141
175
|
|
142
176
|
# Resample each solution path to have resample number of points
|
143
|
-
if resample is not None:
|
177
|
+
if resample is not None and paths is not None:
|
144
178
|
paths = np.array([resample_path(path, resample) for path in paths])
|
145
179
|
|
146
|
-
# Convert distances back to floats in the original scale of the nodes
|
147
|
-
distances = np.array(distances)/1e4
|
148
180
|
return paths, distances
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
181
|
+
|
182
|
+
|
183
|
+
def _get_routes(
|
184
|
+
manager: pywrapcp.RoutingIndexManager,
|
185
|
+
routing: pywrapcp.RoutingModel,
|
186
|
+
solution: pywrapcp.Assignment,
|
187
|
+
num_vehicles: int,
|
188
|
+
start_idx: np.ndarray,
|
189
|
+
end_idx: np.ndarray,
|
190
|
+
trim_paths: bool,
|
191
|
+
) -> Tuple[Optional[List[np.ndarray]], Optional[List[float]]]:
|
192
|
+
"""
|
193
|
+
Solves the Traveling Salesperson Problem (TSP) or Vehicle Routing Problem (VRP)
|
194
|
+
using Google OR-Tools. This method supports multiple vehicles/robots, optional
|
195
|
+
start and end nodes for each vehicle, and an optional maximum distance constraint
|
196
|
+
per path. It also includes a retry mechanism with increased maximum distance
|
197
|
+
if no solution is found.
|
198
|
+
|
199
|
+
Args:
|
200
|
+
nodes (np.ndarray): (# nodes, ndim); A NumPy array of coordinates for all nodes (locations)
|
201
|
+
that need to be visited. `ndim` is the dimensionality (e.g., 2 for 2D, 3 for 3D).
|
202
|
+
num_vehicles (int): The number of vehicles (robots) available to visit the nodes. Defaults to 1.
|
203
|
+
max_dist (float): The maximum allowed travel distance for each vehicle's path.
|
204
|
+
This constraint is particularly relevant for multi-vehicle scenarios. Defaults to 25.0.
|
205
|
+
depth (int): Internal parameter used to track the recursion depth for retries when
|
206
|
+
no solution is found. Users should typically not modify this. Defaults to 1.
|
207
|
+
resample (Optional[int]): If provided, each solution path will be resampled to have
|
208
|
+
exactly `resample` number of points (waypoints). This is useful
|
209
|
+
for standardizing path representations. Defaults to None.
|
210
|
+
start_nodes (Optional[np.ndarray]): (# num_vehicles, ndim); Optional NumPy array specifying
|
211
|
+
the starting coordinates for each vehicle. If None,
|
212
|
+
OR-Tools will find arbitrary start points. Defaults to None.
|
213
|
+
end_nodes (Optional[np.ndarray]): (# num_vehicles, ndim); Optional NumPy array specifying
|
214
|
+
the ending coordinates for each vehicle. If None,
|
215
|
+
OR-Tools will find arbitrary end points. Defaults to None.
|
216
|
+
time_limit (int): The maximum time (in seconds) that OR-Tools will spend searching for a solution.
|
217
|
+
Defaults to 10.
|
218
|
+
|
219
|
+
Returns:
|
220
|
+
Tuple[Optional[List[np.ndarray]], Optional[List[float]]]:
|
221
|
+
- If a solution is found:
|
222
|
+
Tuple[List[np.ndarray], List[float]]: A tuple containing:
|
223
|
+
- paths (List[np.ndarray]): A list of NumPy arrays, where each array
|
224
|
+
represents a vehicle's path (sequence of visited nodes).
|
225
|
+
Shape of each array: (num_waypoints, ndim).
|
226
|
+
- distances (List[float]): A list of floats, where each float is the
|
227
|
+
total length of the corresponding vehicle's path.
|
228
|
+
- If no solution is found after retries:
|
229
|
+
Tuple[None, None]: Returns `(None, None)`.
|
230
|
+
|
231
|
+
Usage:
|
232
|
+
```python
|
233
|
+
import numpy as np
|
234
|
+
from sgptools.utils.tsp import run_tsp
|
235
|
+
|
236
|
+
# Example 1: Single TSP, find best path through 5 points
|
237
|
+
nodes_single = np.array([[0,0], [1,1], [0,2], [2,2], [1,0]], dtype=np.float64)
|
238
|
+
paths_single, dists_single = run_tsp(nodes_single, num_vehicles=1, time_limit=5)
|
239
|
+
|
240
|
+
# Example 2: Multi-robot VRP with start/end nodes and resampling
|
241
|
+
nodes_multi = np.array([[1,1], [2,2], [3,3], [4,4], [5,5], [6,6]], dtype=np.float64)
|
242
|
+
start_points = np.array([[0,0], [7,7]], dtype=np.float64)
|
243
|
+
end_points = np.array([[0,7], [7,0]], dtype=np.float64)
|
244
|
+
|
245
|
+
paths_multi, dists_multi = run_tsp(
|
246
|
+
nodes_multi,
|
247
|
+
num_vehicles=2,
|
248
|
+
max_dist=10.0, # Max distance for each robot
|
249
|
+
resample=10, # Resample each path to 10 points
|
250
|
+
start_nodes=start_points,
|
251
|
+
end_nodes=end_points,
|
252
|
+
time_limit=15
|
253
|
+
)
|
254
|
+
```
|
255
|
+
"""
|
256
|
+
paths: List[np.ndarray] = []
|
257
|
+
distances: List[int] = []
|
156
258
|
for vehicle_id in range(num_vehicles):
|
157
|
-
path = []
|
259
|
+
path: List[int] = []
|
158
260
|
route_distance = 0
|
159
261
|
index = routing.Start(vehicle_id)
|
160
262
|
while not routing.IsEnd(index):
|
@@ -162,39 +264,77 @@ def get_routes(manager, routing, solution, num_vehicles, start_idx, end_idx, tri
|
|
162
264
|
previous_index = index
|
163
265
|
index = solution.Value(routing.NextVar(index))
|
164
266
|
route_distance += routing.GetArcCostForVehicle(
|
165
|
-
previous_index, index, vehicle_id
|
166
|
-
)
|
267
|
+
previous_index, index, vehicle_id)
|
167
268
|
path.append(manager.IndexToNode(index))
|
168
269
|
distances.append(route_distance)
|
169
|
-
|
270
|
+
|
271
|
+
# Remove dummy start/end points if they were added
|
170
272
|
if trim_paths:
|
171
|
-
|
172
|
-
if
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
273
|
+
path_array = np.array(path)
|
274
|
+
# Adjust indices if a dummy node was added at the beginning
|
275
|
+
if (start_idx[vehicle_id] == 0 and path_array[0] == 0
|
276
|
+
and path_array.shape[0] > 1):
|
277
|
+
path_array = path_array[1:]
|
278
|
+
# Adjust indices if a dummy node was added at the end
|
279
|
+
if (end_idx[vehicle_id] == 0 and path_array[-1] == 0
|
280
|
+
and path_array.shape[0] > 0):
|
281
|
+
path_array = path_array[:-1]
|
282
|
+
|
283
|
+
# Shift all indices down by 1 if a dummy node was prepended to the overall distance matrix
|
284
|
+
if np.any(start_idx == 0) or np.any(end_idx == 0):
|
285
|
+
path_array = path_array - 1
|
286
|
+
path_array = path_array[
|
287
|
+
path_array
|
288
|
+
>= 0] # Ensure no negative indices from the shift
|
289
|
+
|
290
|
+
paths.append(path_array)
|
291
|
+
else:
|
292
|
+
paths.append(np.array(path))
|
177
293
|
return paths, distances
|
178
294
|
|
179
|
-
|
180
|
-
|
181
|
-
|
295
|
+
|
296
|
+
def resample_path(waypoints: np.ndarray, num_inducing: int = 10) -> np.ndarray:
|
297
|
+
"""Resamples a given path (sequence of waypoints) to have a fixed number of
|
298
|
+
`num_inducing` points. This is useful for standardizing path representations
|
299
|
+
or for converting a path with an arbitrary number of waypoints into a
|
300
|
+
fixed-size representation for models. The resampling maintains the path's
|
301
|
+
shape and geometric integrity.
|
182
302
|
|
183
303
|
Args:
|
184
|
-
waypoints (ndarray): (num_waypoints, ndim);
|
185
|
-
|
186
|
-
|
304
|
+
waypoints (np.ndarray): (num_waypoints, ndim); A NumPy array representing the
|
305
|
+
waypoints of a path. `num_waypoints` is the original
|
306
|
+
number of points, `ndim` is the dimensionality.
|
307
|
+
num_inducing (int): The desired number of points in the resampled path. Defaults to 10.
|
308
|
+
|
187
309
|
Returns:
|
188
|
-
|
310
|
+
np.ndarray: (num_inducing, ndim); The resampled path with `num_inducing` points.
|
311
|
+
|
312
|
+
Raises:
|
313
|
+
Exception: If the input `ndim` is not 2 or 3 (as `shapely.geometry.LineString`
|
314
|
+
primarily supports 2D/3D geometries).
|
315
|
+
|
316
|
+
Usage:
|
317
|
+
```python
|
318
|
+
import numpy as np
|
319
|
+
from sgptools.utils.tsp import resample_path
|
320
|
+
|
321
|
+
# Example 2D path
|
322
|
+
original_path_2d = np.array([[0,0], [1,5], [3,0], [5,5]], dtype=np.float64)
|
323
|
+
resampled_path_2d = resample_path(original_path_2d, num_inducing=5)
|
324
|
+
|
325
|
+
# Example 3D path
|
326
|
+
original_path_3d = np.array([[0,0,0], [1,1,1], [2,0,2]], dtype=np.float64)
|
327
|
+
resampled_path_3d = resample_path(original_path_3d, num_inducing=7)
|
328
|
+
```
|
189
329
|
"""
|
190
330
|
ndim = np.shape(waypoints)[-1]
|
191
|
-
if not (ndim==2 or ndim==3):
|
331
|
+
if not (ndim == 2 or ndim == 3):
|
192
332
|
raise Exception(f"ndim={ndim} is not supported for path resampling!")
|
193
333
|
line = LineString(waypoints)
|
194
334
|
distances = np.linspace(0, line.length, num_inducing)
|
195
335
|
points = [line.interpolate(distance) for distance in distances]
|
196
|
-
if ndim==2:
|
197
|
-
|
198
|
-
elif ndim==3:
|
199
|
-
|
200
|
-
return
|
336
|
+
if ndim == 2:
|
337
|
+
resampled_points = np.array([[p.x, p.y] for p in points])
|
338
|
+
elif ndim == 3:
|
339
|
+
resampled_points = np.array([[p.x, p.y, p.z] for p in points])
|
340
|
+
return resampled_points
|
@@ -0,0 +1,216 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: sgptools
|
3
|
+
Version: 2.0.0
|
4
|
+
Summary: A Python library for efficient sensor placement and informative path planning
|
5
|
+
Home-page: https://www.SGP-Tools.com
|
6
|
+
Author: Kalvik
|
7
|
+
Author-email: itskalvik@gmail.com
|
8
|
+
License: Apache-2.0
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
10
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
11
|
+
Classifier: Operating System :: OS Independent
|
12
|
+
Requires-Python: >=3.6
|
13
|
+
Description-Content-Type: text/markdown
|
14
|
+
License-File: LICENSE.txt
|
15
|
+
Requires-Dist: apricot-select
|
16
|
+
Requires-Dist: matplotlib
|
17
|
+
Requires-Dist: pandas
|
18
|
+
Requires-Dist: scikit-learn
|
19
|
+
Requires-Dist: scipy
|
20
|
+
Requires-Dist: numpy<2.0.0
|
21
|
+
Requires-Dist: ortools
|
22
|
+
Requires-Dist: scikit-image
|
23
|
+
Requires-Dist: shapely
|
24
|
+
Requires-Dist: cma
|
25
|
+
Requires-Dist: bayesian-optimization
|
26
|
+
Requires-Dist: hkb_diamondsquare
|
27
|
+
Requires-Dist: tensorflow-probability[tf]>=0.21.0
|
28
|
+
Requires-Dist: tensorflow>=2.13.0; platform_machine != "arm64"
|
29
|
+
Requires-Dist: tensorflow-aarch64>=2.13.0; platform_machine == "arm64"
|
30
|
+
Requires-Dist: tensorflow-macos>=2.13.0; platform_system == "Darwin" and platform_machine == "arm64"
|
31
|
+
Requires-Dist: typing_extensions
|
32
|
+
Requires-Dist: gpflow>=2.7.0
|
33
|
+
Requires-Dist: pillow
|
34
|
+
Requires-Dist: geopandas
|
35
|
+
Dynamic: author
|
36
|
+
Dynamic: author-email
|
37
|
+
Dynamic: classifier
|
38
|
+
Dynamic: description
|
39
|
+
Dynamic: description-content-type
|
40
|
+
Dynamic: home-page
|
41
|
+
Dynamic: license
|
42
|
+
Dynamic: license-file
|
43
|
+
Dynamic: requires-dist
|
44
|
+
Dynamic: requires-python
|
45
|
+
Dynamic: summary
|
46
|
+
|
47
|
+
<p align="center">
|
48
|
+
<img src="docs/assets/SGP-Tools.png#gh-light-mode-only" alt="SGP-Tools Logo" width="600"/>
|
49
|
+
<img src="docs/assets/logo_dark.png#gh-dark-mode-only" alt="SGP-Tools Logo" width="600"/>
|
50
|
+
</p>
|
51
|
+
|
52
|
+
<p align="center">
|
53
|
+
<em>A Python library for efficient sensor placement and informative path planning</em>
|
54
|
+
</p>
|
55
|
+
|
56
|
+
<p align="center">
|
57
|
+
<a href="https://pypi.org/project/sgptools/"><img alt="PyPI" src="https://img.shields.io/pypi/v/sgptools.svg"></a>
|
58
|
+
<a href="https://github.com/itskalvik/sgptools/blob/main/LICENSE"><img alt="License" src="https://img.shields.io/pypi/l/sgptools.svg"></a>
|
59
|
+
</p>
|
60
|
+
|
61
|
+
<p align="center">
|
62
|
+
<img src="docs/assets/point_sensing.gif" width="49%">
|
63
|
+
<img src="docs/assets/non-point_sensing.gif" width="49%">
|
64
|
+
<img src="docs/assets/AIPP-4R.gif" width="49%">
|
65
|
+
<img src="docs/assets/AIPP-non-point_sensing.gif" width="49%">
|
66
|
+
</p>
|
67
|
+
|
68
|
+
## What is SGP-Tools?
|
69
|
+
|
70
|
+
**SGP-Tools** is a powerful and flexible Python library designed for solving **Sensor Placement** and **Informative Path Planning** problems, enabling efficient and scalable solutions for environment monitoring, e.g., monitoring air/water quality, soil moisture, or temperature.
|
71
|
+
|
72
|
+
### Sensor Placement
|
73
|
+
|
74
|
+
**Sensor Placement** is the problem of finding ideal locations to deploy a set of static sensors to best monitor a spatial phenomenon. The goal is to select a finite number of locations from a continuous space or a discrete set of candidates to maximize the information gathered about an entire area of interest. This is crucial when deploying a limited number of sensors to cover a large field.
|
75
|
+
|
76
|
+
### Informative Path Planning (IPP)
|
77
|
+
|
78
|
+
**Informative Path Planning** extends this concept to mobile sensors. Instead of finding static locations, IPP aims to compute an informative path for one or more robots to travel along. The path is designed to maximize information gain about the environment, often while adhering to constraints such as a limited travel distance. This is essential for applications like aerial surveying or robotic exploration.
|
79
|
+
|
80
|
+
### IPP vs. Lawnmower Paths
|
81
|
+
|
82
|
+
A common approach to surveying an area is to use a "lawnmower" path, a simple back-and-forth pattern designed for complete coverage. The following table summarizes the key differences between IPP and Lawnmower Paths:
|
83
|
+
|
84
|
+
| Factor | Lawnmower Path | Informative Path Planning (IPP) |
|
85
|
+
| :--- | :--- | :--- |
|
86
|
+
| **Primary Goal** | Complete and uniform coverage of a predefined area. | Targeted data collection in areas of high information or uncertainty. |
|
87
|
+
| **Performance** | Slow data collection but provides a high accuracy reconstruction of the envionment. | Fast data collection but provides an approximate reconstruction of the envionment. |
|
88
|
+
| **Prior Knowledge** | Not required; often used when no prior information is available. | Beneficial, but not required for adaptiev IPP; uses prior information to guide the sampling strategy. |
|
89
|
+
| **Adaptability** | Non-adaptive; the path is fixed before the mission starts. | Highly adaptive; the path is updated in real-time based on sensor data. |
|
90
|
+
| **Efficiency** | Can be inefficient if the phenomenon of interest is sparse. | Highly efficient for sparse or spatially variable phenomena. |
|
91
|
+
| **Computational Cost** | Low; simple to plan and execute. | Medium; requires onboard processing to analyze data and update the path. |
|
92
|
+
| **Best For** | Baseline mapping, homogenous environments, initial surveys. | Dynamic phenomena, resource-constrained missions. |
|
93
|
+
|
94
|
+
## Why SGP-Tools?
|
95
|
+
|
96
|
+
- **State-of-the-Art Algorithms**: Includes a variety of optimization methods including greedy algorithms, Bayesian optimization, CMA-ES, and SGP-based optimization.
|
97
|
+
- **Advanced Modeling Capabilities**: Go beyond simple point sensing with tools for informative path planning for multi-robot systems and complex sensor field-of-view (FoV) models.
|
98
|
+
- **Non-Stationary Kernels**: Capture complex, real-world phenomena with specialized non-stationary kernels like the Neural Spectral Kernel and the Attentive Kernel.
|
99
|
+
- **Flexible and Extensible**: Built on GPflow and TensorFlow, the library is designed to be modular and easy to extend with your own custom methods, kernels, and objectives.
|
100
|
+
|
101
|
+
## Installation
|
102
|
+
The library is available as a ```pip``` package. To install the package, run the following command:
|
103
|
+
|
104
|
+
```
|
105
|
+
python3 -m pip install sgptools
|
106
|
+
```
|
107
|
+
|
108
|
+
Installation from source:
|
109
|
+
|
110
|
+
```
|
111
|
+
git clone https://github.com/itskalvik/sgp-tools.git
|
112
|
+
cd sgp-tools/
|
113
|
+
python3 -m pip install -r requirements.txt
|
114
|
+
python3 -m pip install -e .
|
115
|
+
```
|
116
|
+
|
117
|
+
Note: The requirements.txt file contains packages and their latest versions that were last verified to be working without any issues.
|
118
|
+
|
119
|
+
## Quick Start
|
120
|
+
|
121
|
+
Here's an example of how to use SGP-Tools to get an informative path using the `ContinuousSGP` method:
|
122
|
+
|
123
|
+
```python
|
124
|
+
from sgptools.utils.data import Dataset # Class for loading and managing datasets
|
125
|
+
from sgptools.utils.misc import get_inducing_pts # Utility for selecting inducing points
|
126
|
+
from sgptools.utils.tsp import run_tsp # TSP/VRP solver for initial path planning
|
127
|
+
from sgptools.utils.gpflow import get_model_params # For training initial GP/SGP hyperparameters
|
128
|
+
from sgptools.methods import get_method # Gets the class for continuous SGP optimization
|
129
|
+
from sgptools.core.transformations import IPPTransform # Transforms for IPP
|
130
|
+
|
131
|
+
# 1. Load or generate a dataset
|
132
|
+
# This will create a synthetic dataset for demonstration
|
133
|
+
dataset = Dataset(num_train=500, num_test=10000,
|
134
|
+
shape=(100, 100))
|
135
|
+
X_train, y_train = dataset.get_train()
|
136
|
+
X_test, y_test = dataset.get_test()
|
137
|
+
|
138
|
+
# 2. Learn the GP hyperparameters from the training data
|
139
|
+
print("Learning GP hyperparameters...")
|
140
|
+
_, noise_variance, kernel = get_model_params(
|
141
|
+
X_train, y_train, max_steps=1000, verbose=True
|
142
|
+
)
|
143
|
+
|
144
|
+
# 3. Setup the IPP model
|
145
|
+
num_placements = 15
|
146
|
+
|
147
|
+
# Initialize inducing points and get initial path
|
148
|
+
Xu_init = get_inducing_pts(X_train, num_placements)
|
149
|
+
Xu_init, _ = run_tsp(Xu_init, time_limit=10)
|
150
|
+
|
151
|
+
# Setup IPP transform with a sampling rate for continuous sensing
|
152
|
+
transform_continuous_sensing = IPPTransform(sampling_rate=4)
|
153
|
+
|
154
|
+
# Initialize the ContinuousSGP model
|
155
|
+
method = get_method('ContinuousSGP')
|
156
|
+
csgp_optimizer = method(
|
157
|
+
num_placements,
|
158
|
+
X_train,
|
159
|
+
kernel,
|
160
|
+
noise_variance,
|
161
|
+
transform_continuous_sensing,
|
162
|
+
X_init=Xu_init[0]
|
163
|
+
)
|
164
|
+
|
165
|
+
# 4. Run the optimization
|
166
|
+
print("Optimizing sensor placements...")
|
167
|
+
solution_path = csgp_optimizer.optimize(max_steps=200)
|
168
|
+
|
169
|
+
print(f"Solution Path: {solution_path}")
|
170
|
+
```
|
171
|
+
|
172
|
+
<p align="center">
|
173
|
+
<img src="docs/assets/quick_start.png" width="600">
|
174
|
+
</p>
|
175
|
+
|
176
|
+
For more detailed instructions, please refer to our [tutorials](http://sgp-tools.com/tutorials/index.html)
|
177
|
+
|
178
|
+
## SGP-based IPP
|
179
|
+
[](https://www.youtube.com/embed/G-RKFa1vNHM?si=PLmrmkCwXRj7mc4A "Video Summary")
|
180
|
+
|
181
|
+
## Datasets
|
182
|
+
High-resolution topography and bathymetry data can be downloaded from [NOAA Digital Coast](https://coast.noaa.gov/digitalcoast/)
|
183
|
+
|
184
|
+
## About
|
185
|
+
Please consider citing the following papers if you use SGP-Tools in your academic work 😄
|
186
|
+
|
187
|
+
```
|
188
|
+
@misc{JakkalaA23SP,
|
189
|
+
AUTHOR={Kalvik Jakkala and Srinivas Akella},
|
190
|
+
TITLE={Efficient Sensor Placement from Regression with Sparse Gaussian Processes in Continuous and Discrete Spaces},
|
191
|
+
NOTE= {Preprint},
|
192
|
+
YEAR={2023},
|
193
|
+
URL={https://www.itskalvik.com/research/publication/sgp-sp/},
|
194
|
+
}
|
195
|
+
|
196
|
+
@inproceedings{JakkalaA24IPP,
|
197
|
+
AUTHOR={Kalvik Jakkala and Srinivas Akella},
|
198
|
+
TITLE={Multi-Robot Informative Path Planning from Regression with Sparse Gaussian Processes},
|
199
|
+
booktitle={IEEE International Conference on Robotics and Automation, {ICRA}},
|
200
|
+
YEAR={2024},
|
201
|
+
PUBLISHER = {{IEEE}},
|
202
|
+
URL={https://www.itskalvik.com/research/publication/sgp-ipp/}
|
203
|
+
}
|
204
|
+
|
205
|
+
@inproceedings{JakkalaA25AIPP,
|
206
|
+
AUTHOR={Kalvik Jakkala and Srinivas Akella},
|
207
|
+
TITLE={Fully Differentiable Adaptive Informative Path Planning},
|
208
|
+
booktitle={IEEE International Conference on Robotics and Automation, {ICRA}},
|
209
|
+
YEAR={2025},
|
210
|
+
PUBLISHER = {{IEEE}},
|
211
|
+
URL={https://www.itskalvik.com/research/publication/sgp-aipp/}
|
212
|
+
}
|
213
|
+
```
|
214
|
+
|
215
|
+
## Acknowledgements
|
216
|
+
This work was funded in part by the UNC Charlotte Office of Research and Economic Development and by NSF under Award Number IIP-1919233.
|
@@ -0,0 +1,23 @@
|
|
1
|
+
sgptools/__init__.py,sha256=G2en4FsMUDwwaPFYm6zJX8rMAqMiWGRv3F5izvugFJA,442
|
2
|
+
sgptools/methods.py,sha256=KB7hCs_EqoC-EpSB60ywCWdSGXThR-i_JRpI6WBoN20,46274
|
3
|
+
sgptools/objectives.py,sha256=8rhKz-s7rwLRN-nHptGUTItmYhOzc0hNkNsSx2NVQGg,10655
|
4
|
+
sgptools/core/__init__.py,sha256=OA3EJxpq93HD1_8dlbusW9WoP0BgCrXhJbA7c794Vu0,28
|
5
|
+
sgptools/core/augmented_gpr.py,sha256=xURuUSLrWltAMiM7cMTMV_AqUBh3cxESunuO_eUY7sc,3428
|
6
|
+
sgptools/core/augmented_sgpr.py,sha256=ZtCsYB1hEq0PW0kAm5-N-EEPOgPRltL2MihSHmHPdYc,7230
|
7
|
+
sgptools/core/osgpr.py,sha256=CnpBg5RQjylFqhUL0tcrSXlRcmek6ajtZ9VmTxuv7ao,18912
|
8
|
+
sgptools/core/transformations.py,sha256=GxHkCUhO4639TtaSquNAYFJT-RymO_I3nzfWKv77ibY,33686
|
9
|
+
sgptools/kernels/__init__.py,sha256=G-o4JY9Hg2i9YBwp6B95nbLfqKxdZ1SkyhsplrSdBds,31
|
10
|
+
sgptools/kernels/attentive_kernel.py,sha256=6Ua2GymZSAurQPszrMQaCnbnfIz8tBX5raCWhPrYR64,11796
|
11
|
+
sgptools/kernels/neural_kernel.py,sha256=jo9NqyzRcpMrRNyHTmLUvLD2KWbEkDiiEVPb6Z5FmkI,15506
|
12
|
+
sgptools/kernels/neural_network.py,sha256=bVmS0o3eAags4SBE_7NXqC2KIK9F7kM2pDFESvpihH0,6872
|
13
|
+
sgptools/utils/__init__.py,sha256=Dr5d9vCoeNGGtkebjQ6pSTghOrjm_zycmPkfRdcCHWo,29
|
14
|
+
sgptools/utils/data.py,sha256=8KU4befi5SkmsRJ5W7LGZDhAchFGr2jnfz82PBvfZxQ,22864
|
15
|
+
sgptools/utils/gpflow.py,sha256=FPy8_ST-IJr-sdcjZPB3iu-Dae_SkCSofbULxRc2O-o,19970
|
16
|
+
sgptools/utils/metrics.py,sha256=M8Otd9Iiv6522OaAuhYRF7x2WeSvNdtjxtyhUneqAMU,18290
|
17
|
+
sgptools/utils/misc.py,sha256=ZkPz3kHuy_1zcnnA74mJ1GjveWHF6eTZK2yX2JWOPI0,8686
|
18
|
+
sgptools/utils/tsp.py,sha256=wIyg91KdJh_zsQ7_nOCgV2BIYKoxEE1tCVYFORU3Yrg,15257
|
19
|
+
sgptools-2.0.0.dist-info/licenses/LICENSE.txt,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
20
|
+
sgptools-2.0.0.dist-info/METADATA,sha256=ggG8j2395qCiNB0g2q8SQpNkHujLOiWUA1xoZcQA1O0,9713
|
21
|
+
sgptools-2.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
22
|
+
sgptools-2.0.0.dist-info/top_level.txt,sha256=2NWH6uQLAOuLB9fG7o1pqf6Jvpe1_hEcuqfSqtUw3gw,9
|
23
|
+
sgptools-2.0.0.dist-info/RECORD,,
|
sgptools/models/__init__.py
DELETED
@@ -1,10 +0,0 @@
|
|
1
|
-
# sgptools/models/__init__.py
|
2
|
-
|
3
|
-
"""Sensor placement and informative path planning methods in this package:
|
4
|
-
|
5
|
-
- `bo`: Provides a Bayesian optimization based approach that maximizes mutual-information to get sensor placements
|
6
|
-
- `cma_es`: Provides a genetic algorithm (CMA-ES) based approach that maximizes mutual-information to get sensor placements
|
7
|
-
- `continuous_sgp`: Provides an SGP-based sensor placement approach that is optimized using gradient descent
|
8
|
-
- `greedy_mi`: Provides a greedy algorithm based approach that maximizes mutual-information to get sensor placements
|
9
|
-
- `greedy_sgp`: Provides an SGP-based sensor placement approach that is optimized using a greedy algorithm
|
10
|
-
"""
|