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/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(nodes,
23
- num_vehicles=1,
24
- max_dist=25,
25
- depth=1,
26
- resample=None,
27
- start_nodes=None,
28
- end_nodes=None,
29
- time_limit=10):
30
- """Method to run TSP/VRP with arbitrary start and end nodes,
31
- and without any distance constraint
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 mutli-robot case
37
- depth (int): Internal parameter used to track re-try recursion depth
38
- resample (int): Each solution path will be resampled to have
39
- `resample` number of points
40
- start_nodes (ndarray): (# num_vehicles, ndim); Optionl array of start nodes from which
41
- to start each vehicle's solution path
42
- end_nodes (ndarray): (# num_vehicles, ndim); Optionl array of end nodes at which
43
- to end each vehicle's solution path
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
- paths (ndarray): Solution paths
48
- distances (list): List of path lengths
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 #shift to account for dummy node
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
- paths, distances = get_routes(manager, routing,
129
- solution, num_vehicles,
130
- start_idx, end_idx, trim_paths)
131
- for path in paths:
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('TSP Warning: Empty path detected')
134
- return run_tsp(nodes, num_vehicles, int(np.mean(distances)*(1.5/depth)), depth+1)
135
- else:
136
- print('TSP Warning: No solution found')
137
- return run_tsp(nodes, num_vehicles, int(max_dist*1.5), depth+1)
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
- # Map paths from node indices to node locations
140
- paths = [nodes[path] for path in paths]
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
- Method to extract route from or-tools solution
152
- '''
153
- def get_routes(manager, routing, solution, num_vehicles, start_idx, end_idx, trim_paths):
154
- paths = []
155
- distances = []
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
- # remove dummy start/end point
270
+
271
+ # Remove dummy start/end points if they were added
170
272
  if trim_paths:
171
- path = np.array(path)-1
172
- if start_idx[vehicle_id] == 0:
173
- path = path[1:]
174
- if end_idx[vehicle_id] == 0:
175
- path = path[:-1]
176
- paths.append(path)
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
- def resample_path(waypoints, num_inducing=10):
180
- """Function to map path with arbitrary number of waypoints to
181
- inducing points path with fixed number of waypoints
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); waypoints of path from vrp solver
185
- num_inducing (int): Number of inducing points (waypoints) in the returned path
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
- points (ndarray): (num_inducing, ndim); Resampled path
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
- points = np.array([[p.x, p.y] for p in points])
198
- elif ndim==3:
199
- points = np.array([[p.x, p.y, p.z] for p in points])
200
- return points
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
+ [![Video Summary](https://res.cloudinary.com/marcomontalbano/image/upload/v1713536416/video_to_markdown/images/youtube--G-RKFa1vNHM-c05b58ac6eb4c4700831b2b3070cd403.jpg)](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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (76.0.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -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
- """