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.
@@ -1,434 +0,0 @@
1
- # Copyright 2024 The SGP-Tools Contributors. All Rights Reserved.
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
-
15
- """Provides transforms to model complex sensor field of views and handle informative path planning
16
- """
17
-
18
- import tensorflow as tf
19
- import numpy as np
20
-
21
-
22
- class Transform:
23
- """Base class for transformations of the inducing points, including expansion and aggregation transforms.
24
-
25
- Refer to the following papers for more details:
26
- - Efficient Sensor Placement from Regression with Sparse Gaussian Processes in Continuous and Discrete Spaces [Jakkala and Akella, 2023]
27
- - Multi-Robot Informative Path Planning from Regression with Sparse Gaussian Processes [Jakkala and Akella, 2024]
28
-
29
- Args:
30
- aggregation_size (int): Number of consecutive inducing points to aggregate
31
- constraint_weight (float): Weight term that controls the importance of the
32
- constraint terms in the SGP's optimization objective
33
- """
34
- def __init__(self,
35
- aggregation_size=None,
36
- constraint_weight=1.0,
37
- **kwargs):
38
- self.aggregation_size = aggregation_size
39
- self.constraint_weight = constraint_weight
40
-
41
- def expand(self, Xu):
42
- """Applies the expansion transform to the inducing points
43
-
44
- Args:
45
- Xu (ndarray): Expansion transformed inducing points
46
- """
47
- return Xu
48
-
49
- def aggregate(self, k):
50
- """Applies the aggregation transform to kernel matrices
51
-
52
- Args:
53
- k (tensor): (mp, mp)/(mp, n); Kernel matrix.
54
- `m` is the number of inducing points,
55
- `p` is the number of points each inducing point is mapped,
56
- `n` is the number of training data points.
57
-
58
- Returns:
59
- k (tensor): (m, m)/(m, n); Aggregated kernel matrix
60
- """
61
- if self.aggregation_size is None:
62
- return k
63
-
64
- if k.shape[0] == k.shape[1]:
65
- # Handle Kuu which is a square matrix
66
- k = tf.expand_dims(tf.expand_dims(k, axis=0), axis=-1)
67
- k = tf.nn.avg_pool(k,
68
- ksize=[1, self.aggregation_size, self.aggregation_size, 1],
69
- strides=[1, self.aggregation_size, self.aggregation_size, 1],
70
- padding='VALID')
71
- k = tf.squeeze(k, axis=[0, -1])
72
- else:
73
- # Handle Kuf which is a rectangular matrix
74
- k = tf.expand_dims(k, axis=0)
75
- k = tf.nn.avg_pool(k,
76
- ksize=[1, self.aggregation_size, 1],
77
- strides=[1, self.aggregation_size, 1],
78
- padding='VALID')
79
- k = tf.squeeze(k, axis=[0])
80
- return k
81
-
82
- def constraints(self, Xu):
83
- """Computes the constraint terms that are added to the SGP's optimization function
84
-
85
- Args:
86
- Xu (ndarray): Inducing points from which to compute the constraints
87
-
88
- Returns:
89
- c (float): constraint terms (eg., distance constraint)
90
- """
91
- return 0.
92
-
93
-
94
- class IPPTransform(Transform):
95
- """Transform to model IPP problems
96
-
97
- Usage details:
98
- * For point sensing, set `sampling_rate = 2`
99
- * For continuous sensing, set `sampling_rate > 2` (account for the information along the path)
100
- * For continuous sensing with aggregation, set `sampling_rate > 2` and `aggregate_fov = True` (faster but solution quality is a bit diminished)
101
- * If using a non-point FoV model with continuous sampling, only the FoV inducing points are aggregated
102
- * For multi-robot case, set `num_robots > 1`
103
- * For onlineIPP use `update_fixed` to freeze the visited waypoints
104
-
105
- Args:
106
- sampling_rate (int): Number of points to sample between each pair of inducing points
107
- distance_budget (float): Distance budget for the path
108
- num_robots (int): Number of robots
109
- Xu_fixed (ndarray): (num_robots, num_visited, num_dim); Visited waypoints that don't need to be optimized
110
- num_dim (int): Number of dimensions of the inducing points
111
- sensor_model (Transform): Transform object to expand each inducing point to `p` points
112
- approximating each sensor's FoV
113
- aggregate_fov (bool): Used only when sampling_rate > 2, i.e., when using a continuous sensing model.
114
- If `True`, covariances corresponding to interpolated inducing points along each edge
115
- of the path are aggregated to reduce the matrix inversion cost.
116
- """
117
- def __init__(self,
118
- sampling_rate=2,
119
- distance_budget=None,
120
- num_robots=1,
121
- Xu_fixed=None,
122
- num_dim=2,
123
- sensor_model=None,
124
- aggregate_fov=False,
125
- **kwargs):
126
- super().__init__(**kwargs)
127
- if sampling_rate < 2:
128
- raise ValueError('Sampling rate must be greater than 2.')
129
-
130
- self.sampling_rate = sampling_rate
131
- self.distance_budget = distance_budget
132
- self.num_robots = num_robots
133
- self.num_dim = num_dim
134
- self.sensor_model = sensor_model
135
-
136
- # Set aggregation size to sampling rate if aggregate_fov is True
137
- # and sampling rate is enabled (greater than 2)
138
- if aggregate_fov:
139
- if self.sensor_model is not None:
140
- self.sensor_model.enable_aggregation()
141
- elif sampling_rate > 2:
142
- self.aggregation_size = sampling_rate
143
-
144
- # Initilize variable to store visited waypoints for onlineIPP
145
- if Xu_fixed is not None:
146
- self.Xu_fixed = tf.Variable(Xu_fixed,
147
- shape=tf.TensorShape(None),
148
- trainable=False)
149
- else:
150
- self.Xu_fixed = None
151
-
152
- def update_Xu_fixed(self, Xu_fixed):
153
- """Function to update the visited waypoints
154
-
155
- Args:
156
- Xu_fixed (ndarray): numpy array (num_robots, num_visited_waypoints, num_dim)
157
- """
158
- self.num_fixed = Xu_fixed.shape[1]
159
- if self.Xu_fixed is not None:
160
- self.Xu_fixed.assign(Xu_fixed)
161
- else:
162
- self.Xu_fixed = tf.Variable(Xu_fixed,
163
- shape=tf.TensorShape(None),
164
- trainable=False)
165
-
166
- def expand(self, Xu, expand_sensor_model=True):
167
- """Sample points between each pair of inducing points to form the path
168
-
169
- Args:
170
- Xu (ndarray): (num_robots x num_inducing, num_dim); Inducing points in the num_dim dimensional space
171
- expand_sensor_model (bool): Only add the fixed inducing points without other sensor/path transforms,
172
- used for online IPP
173
-
174
- Returns:
175
- Xu (ndarray): Expansion transformed inducing points
176
- """
177
- # If using single-robot offline IPP with point sensing, return inducing points as is.
178
- if self.sampling_rate == 2 and self.Xu_fixed is None and self.sensor_model is None:
179
- return Xu
180
-
181
- Xu = tf.reshape(Xu, (self.num_robots, -1, self.num_dim))
182
-
183
- # If using online IPP, add visited waypoints that won't be optimized anymore
184
- if self.Xu_fixed is not None:
185
- Xu = tf.concat([self.Xu_fixed, Xu[:, self.num_fixed:]], axis=1)
186
-
187
- if not expand_sensor_model:
188
- return tf.reshape(Xu, (-1, self.num_dim))
189
-
190
- # Interpolate additional inducing points between waypoints to approximate
191
- # the continuous data sensing model
192
- if self.sampling_rate > 2:
193
- Xu = tf.linspace(Xu[:, :-1], Xu[:, 1:], self.sampling_rate)
194
- Xu = tf.transpose(Xu, perm=[1, 2, 0, 3])
195
- Xu = tf.reshape(Xu, (self.num_robots, -1, self.num_dim))
196
-
197
- if self.sensor_model is not None:
198
- Xu_ = []
199
- for i in range(self.num_robots):
200
- Xu_.append(self.sensor_model.expand(Xu[i]))
201
- Xu = tf.concat(Xu_, axis=0)
202
- return Xu
203
-
204
- Xu = tf.reshape(Xu, (-1, self.num_dim))
205
- return Xu
206
-
207
- def aggregate(self, k):
208
- """Applies the aggregation transform to kernel matrices. Checks `sensor_model`
209
- and uses the appropriate aggregation transform.
210
-
211
- Args:
212
- k (tensor): (mp, mp)/(mp, n); Kernel matrix.
213
- `m` is the number of inducing points,
214
- `p` is the number of points each inducing point is mapped,
215
- `n` is the number of training data points.
216
-
217
- Returns:
218
- k (tensor): (m, m)/(m, n); Aggregated kernel matrix
219
- """
220
- if self.sensor_model is not None:
221
- return self.sensor_model.aggregate(k)
222
- else:
223
- return super().aggregate(k)
224
-
225
- def constraints(self, Xu):
226
- """Computes the distance constraint term that is added to the SGP's optimization function.
227
- Each robot can be assigned a different distance budget.
228
-
229
- Args:
230
- Xu (ndarray): Inducing points from which to compute the distance constraints
231
-
232
- Returns:
233
- loss (float): distance constraint term
234
- """
235
- if self.distance_budget is None:
236
- return 0.
237
- else:
238
- # Only do fixed points expansion transform
239
- Xu = self.expand(Xu, expand_sensor_model=False)
240
- dist = self.distance(Xu)-self.distance_budget
241
- dist = tf.reduce_sum(tf.nn.relu(dist))
242
- loss = -dist*self.constraint_weight
243
- return loss
244
-
245
- def distance(self, Xu):
246
- """Computes the distance incured by sequentially visiting the inducing points
247
- Args:
248
- Xu (ndarray): (m, num_dim); Inducing points from which to compute the path lengths
249
- `m` is the number of inducing points
250
- `num_dim` dimension of the data collection environment
251
- Returns:
252
- dist (float or tensor of floats): path length(s)
253
- """
254
- Xu = tf.reshape(Xu, (self.num_robots, -1, self.num_dim))
255
- if self.sensor_model is not None:
256
- dists = []
257
- for i in range(self.num_robots):
258
- dists.append(self.sensor_model.distance(Xu[i]))
259
- dists = tf.concat(dists, axis=0)
260
- return dists
261
- else:
262
- # Assumes 2D waypoints by default
263
- dist = tf.norm(Xu[:, 1:, :2] - Xu[:, :-1, :2], axis=-1)
264
- dist = tf.reduce_sum(dist, axis=1)
265
- return dist
266
-
267
-
268
- class SquareTransform(Transform):
269
- """Non-point Transform to model a square FoV. Only works for single robot cases.
270
-
271
- Args:
272
- length (float): Length of the square FoV
273
- num_side (int): Number of points along each side of the FoV
274
- aggregate_fov (bool): If `True`, covariances corresponding to interpolated inducing points used to
275
- approximate the sensor FoV are aggregated to reduce the matrix inversion cost
276
- """
277
- def __init__(self, length, num_side, aggregate_fov=False, **kwargs):
278
- super().__init__(**kwargs)
279
- self.length = length
280
- self.num_side = num_side
281
- self.length_factor=length/(self.num_side)
282
- self.num_length = int(length/self.length_factor)
283
-
284
- if aggregate_fov:
285
- self.enable_aggregation()
286
-
287
- def enable_aggregation(self, size=None):
288
- """Enable FoV covariance aggregation, which reduces the covariance matrix inversion cost by reducing the
289
- covariance matrix size.
290
-
291
- Args:
292
- size (int): If None, all the interpolated inducing points within the FoV are aggregated. Alternatively,
293
- the number of inducing points to aggregate can be explicitly defined using this variable.
294
- """
295
- if size is None:
296
- self.aggregation_size = self.num_side**2
297
- else:
298
- self.aggregation_size = size
299
-
300
- def expand(self, Xu):
301
- """Applies the expansion transformation to the inducing points
302
-
303
- Args:
304
- Xu (ndarray): (m, 3); Inducing points in the position and orientation space.
305
- `m` is the number of inducing points,
306
- `3` is the dimension of the space (x, y, angle in radians)
307
-
308
- Returns:
309
- Xu (ndarray): (mp, 2); Inducing points in input space.
310
- `p` is the number of points each inducing point is mapped
311
- to in order to form the FoV.
312
- """
313
- x, y, theta = tf.split(Xu, num_or_size_splits=3, axis=1)
314
- x = tf.reshape(x, [-1,])
315
- y = tf.reshape(y, [-1,])
316
- theta = tf.reshape(theta, [-1,])
317
-
318
- points = []
319
- for i in range(-int(np.floor((self.num_side)/2)), int(np.ceil((self.num_side)/2))):
320
- points.append(tf.linspace([(x + (i * self.length_factor) * tf.cos(theta)) - self.length/2 * tf.cos(theta+np.pi/2),
321
- (y + (i * self.length_factor) * tf.sin(theta)) - self.length/2 * tf.sin(theta+np.pi/2)],
322
- [(x + (i * self.length_factor) * tf.cos(theta)) + self.length/2 * tf.cos(theta+np.pi/2),
323
- (y + (i * self.length_factor) * tf.sin(theta)) + self.length/2 * tf.sin(theta+np.pi/2)],
324
- self.num_side, axis=1))
325
- xy = tf.concat(points, axis=1)
326
- xy = tf.transpose(xy, [2, 1, 0])
327
- xy = tf.reshape(xy, (-1, 2))
328
- return xy
329
-
330
- def distance(self, Xu):
331
- """Computes the distance incured by sequentially visiting the inducing points
332
- Args:
333
- Xu (ndarray): (m, 3); Inducing points from which to compute the path lengths.
334
- `m` is the number of inducing points.
335
-
336
- Returns:
337
- dist (float): path lengths
338
- """
339
- Xu = tf.reshape(Xu, (-1, 3))[:, :2]
340
- dist = tf.norm(Xu[1:] - Xu[:-1], axis=-1)
341
- dist = tf.reduce_sum(dist, axis=0)
342
- return dist
343
-
344
-
345
- class SquareHeightTransform(Transform):
346
- """Non-point Transform to model a height-dependent square FoV
347
-
348
- Args:
349
- num_side (int): Number of points along each side of the FoV
350
- aggregate_fov (bool): If `True`, covariances corresponding to interpolated inducing points used to
351
- approximate the sensor FoV are aggregated to reduce the matrix inversion cost
352
- """
353
- def __init__(self, num_side, aggregate_fov=False, **kwargs):
354
- super().__init__(**kwargs)
355
- self.num_side = num_side
356
-
357
- if aggregate_fov:
358
- self.enable_aggregation()
359
-
360
- def enable_aggregation(self, size=None):
361
- """Enable FoV covariance aggregation, which reduces the covariance matrix inversion cost by reducing the
362
- covariance matrix size.
363
-
364
- Args:
365
- size (int): If None, all the interpolated inducing points within the FoV are aggregated. Alternatively,
366
- the number of inducing points to aggregate can be explicitly defined using this variable.
367
- """
368
- if size is None:
369
- self.aggregation_size = self.num_side**2
370
- else:
371
- self.aggregation_size = size
372
-
373
- def expand(self, Xu):
374
- """
375
- Applies the expansion transform to the inducing points
376
-
377
- Args:
378
- Xu (ndarray): (m, 3); Inducing points in the 3D position space.
379
- `m` is the number of inducing points,
380
- `3` is the dimension of the space (x, y, z)
381
-
382
- Returns:
383
- Xu (ndarray): (mp, 2); Inducing points in input space.
384
- `p` is the number of points each inducing point is mapped
385
- to in order to form the FoV.
386
- """
387
- x, y, h = tf.split(Xu, num_or_size_splits=3, axis=1)
388
- x = tf.reshape(x, [-1,])
389
- y = tf.reshape(y, [-1,])
390
- h = tf.reshape(h, [-1,])
391
-
392
- delta = h / (self.num_side - 1)
393
-
394
- pts = []
395
- for i in range(self.num_side):
396
- pts.append(tf.linspace([x - h/2, y - (h/2) + (delta * i)],
397
- [x + h/2, y - (h/2) + (delta * i)],
398
- self.num_side,
399
- axis=1))
400
- xy = tf.concat(pts, axis=1)
401
- xy = tf.transpose(xy, [2, 1, 0])
402
- xy = tf.reshape(xy, [-1, 2])
403
- xy = self._reshape(xy, tf.shape(Xu)[0])
404
- return xy
405
-
406
- def _reshape(self, X, num_inducing):
407
- """Reorder the inducing points to be in the correct order for aggregation with square height FoV
408
-
409
- Args:
410
- X (ndarray): (mp, 2); Inducing points in input space. `p` is the number of points each
411
- inducing point is mapped to in order to form the FoV.
412
-
413
- Returns:
414
- Xu (ndarray): (mp, 2); Reorder inducing points
415
- """
416
- X = tf.reshape(X, (num_inducing, -1, self.num_side, self.num_side, 2))
417
- X = tf.transpose(X, (0, 2, 1, 3, 4))
418
- X = tf.reshape(X, (-1, 2))
419
- return X
420
-
421
- def distance(self, Xu):
422
- """Computes the distance incured by sequentially visiting the inducing points
423
- Args:
424
- Xu (ndarray): (m, 3); Inducing points from which to compute the path lengths.
425
- `m` is the number of inducing points.
426
-
427
- Returns:
428
- dist (float): path lengths
429
- """
430
- Xu = tf.reshape(Xu, (-1, 3))
431
- dist = tf.norm(Xu[1:] - Xu[:-1], axis=-1)
432
- dist = tf.reduce_sum(dist, axis=0)
433
- return dist
434
-
@@ -1,115 +0,0 @@
1
- # Copyright 2024 The SGP-Tools Contributors. All Rights Reserved.
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
-
15
- from apricot import CustomSelection
16
- from gpflow.models.gpr import GPR
17
- import numpy as np
18
-
19
-
20
- class GreedyMI:
21
- """Helper class to compute mutual information using a Gaussian process for a given set of sensor locations.
22
- Used by `get_greedy_mi_sol` function to compute the solution sensor placements using the Greedy-MI method.
23
-
24
- Refer to the following papers for more details:
25
- - Near-Optimal Sensor Placements in Gaussian Processes: Theory, Efficient Algorithms and Empirical Studies [Krause et al., 2008]
26
- - Data-driven learning and planning for environmental sampling [Ma et al., 2018]
27
-
28
- Args:
29
- S (ndarray): (n, d); Candidate sensor placement locations
30
- V (ndarray): (n, d); Locations in the environment used to approximate the monitoring regions
31
- noise_variance (float): data variance
32
- kernel (gpflow.kernels.Kernel): gpflow kernel function
33
- transform (Transform): Transform object
34
- """
35
- def __init__(self, S, V, noise_variance, kernel,
36
- transform=None):
37
- self.S = S
38
- self.V = V
39
- self.kernel = kernel
40
- self.input_dim = S.shape[1]
41
- self.noise_variance = noise_variance
42
- self.transform = transform
43
-
44
- def mutual_info(self, x):
45
- """Computes mutual information using the points `x`
46
-
47
- Args:
48
- x (ndarray): (n); Indices of the solution placement locations
49
-
50
- Returns:
51
- MI (float): Mutual information between the placement x and candidate locations
52
- """
53
- x = np.array(x).reshape(-1).astype(int)
54
- A = self.S[x[:-1]].reshape(-1, self.input_dim)
55
- y = self.S[x[-1]].reshape(-1, self.input_dim)
56
-
57
- if len(A) == 0:
58
- sigma_a = 1.0
59
- else:
60
- if self.transform is not None:
61
- A = self.transform.expand(A)
62
- a_gp = GPR(data=(A, np.zeros((len(A), 1))),
63
- kernel=self.kernel,
64
- noise_variance=self.noise_variance)
65
- _, sigma_a = a_gp.predict_f(y)
66
-
67
- # Remove locations in A∪y from V to build A bar (Refer to Krause et al., 2008)
68
- V_ = self.V.copy()
69
- V_rows = V_.view([('', V_.dtype)] * V_.shape[1])
70
-
71
- if self.transform is not None:
72
- solution = self.S[x].reshape(-1, self.input_dim)
73
- A_ = self.transform.expand(solution)
74
- else:
75
- A_ = self.S[x]
76
- A_rows = A_.view([('', V_.dtype)] * A_.shape[1])
77
-
78
- V_ = np.setdiff1d(V_rows, A_rows).view(V_.dtype).reshape(-1, V_.shape[1])
79
-
80
- self.v_gp = GPR(data=(V_, np.zeros((len(V_), 1))),
81
- kernel=self.kernel,
82
- noise_variance=self.noise_variance)
83
- _, sigma_v = self.v_gp.predict_f(y)
84
-
85
- return (sigma_a/sigma_v).numpy().squeeze()
86
-
87
-
88
- def get_greedy_mi_sol(num_sensors, candidates, X_train, noise_variance, kernel,
89
- transform=None, optimizer='naive'):
90
- """Get sensor placement solutions using the GP-based mutual information approach (submodular objective function).
91
- Uses a greedy algorithm to select sensor placements from a given discrete set of candidates locations.
92
-
93
- Refer to the following papers for more details:
94
- - Near-Optimal Sensor Placements in Gaussian Processes: Theory, Efficient Algorithms and Empirical Studies [Krause et al., 2008]
95
- - Data-driven learning and planning for environmental sampling [Ma et al., 2018]
96
-
97
- Args:
98
- num_sensors (int): Number of sensor locations to optimize
99
- candidates (ndarray): (n, d); Candidate sensor placement locations
100
- X_train (ndarray): (n, d); Locations in the environment used to approximate the monitoring regions
101
- noise_variance (float): data variance
102
- kernel (gpflow.kernels.Kernel): gpflow kernel function
103
- transform (Transform): Transform object
104
- optimizer (str): Name of an optimizer available in the apricot library
105
-
106
- Returns:
107
- Xu (ndarray): (m, d); Solution sensor placement locations
108
- """
109
- mi_model = GreedyMI(candidates, X_train, noise_variance, kernel, transform)
110
- model = CustomSelection(num_sensors,
111
- mi_model.mutual_info,
112
- optimizer=optimizer,
113
- verbose=False)
114
- sol = model.fit_transform(np.arange(len(candidates)).reshape(-1, 1))
115
- return candidates[sol.reshape(-1)]
@@ -1,97 +0,0 @@
1
- # Copyright 2024 The SGP-Tools Contributors. All Rights Reserved.
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
-
15
- from .core.augmented_sgpr import AugmentedSGPR
16
- from apricot import CustomSelection
17
- import numpy as np
18
-
19
-
20
- class GreedySGP:
21
- """Helper class to compute SGP's ELBO/optimization bound for a given set of sensor locations.
22
- Used by `get_greedy_sgp_sol` function to compute the solution sensor placements using the Greedy-SGP method.
23
-
24
- Refer to the following papers for more details:
25
- - Efficient Sensor Placement from Regression with Sparse Gaussian Processes in Continuous and Discrete Spaces [[Jakkala and Akella, 2023](https://www.itskalvik.com/publication/sgp-sp/)]
26
-
27
- Args:
28
- num_inducing (int): Number of inducing points
29
- S (ndarray): (n, d); Candidate sensor placement locations
30
- V (ndarray): (n, d); Locations in the environment used to approximate the monitoring regions
31
- noise_variance (float): Data noise variance
32
- kernel (gpflow.kernels.Kernel): gpflow kernel function
33
- transform (Transform): Transform object
34
- """
35
- def __init__(self, num_inducing, S, V, noise_variance, kernel,
36
- transform=None):
37
- self.sgp = AugmentedSGPR((V, np.zeros((len(V), 1))),
38
- noise_variance=noise_variance,
39
- kernel=kernel,
40
- inducing_variable=S[:num_inducing],
41
- transform=transform)
42
- self.locs = S
43
- self.num_inducing = num_inducing
44
- self.inducing_dim = S.shape[1]
45
-
46
- def bound(self, x):
47
- """Computes the SGP's optimization bound using the inducing points `x`
48
-
49
- Args:
50
- x (ndarray): (n); Indices of the solution placement locations
51
-
52
- Returns:
53
- elbo (float): Evidence lower bound/SGP's optimization bound value
54
- """
55
- x = np.array(x).reshape(-1).astype(int)
56
- Xu = np.ones((self.num_inducing, self.inducing_dim), dtype=np.float32)
57
-
58
- # Initialize all inducing points at the first solution placement location.
59
- # Ensures that the number of inducing points is always fixed and no additional
60
- # information is passed to the SGP
61
- Xu *= self.locs[x][0]
62
-
63
- # Copy all given solution placements to the inducing points set
64
- Xu[-len(x):] = self.locs[x]
65
-
66
- # Update the SGP inducing points
67
- self.sgp.inducing_variable.Z.assign(Xu)
68
- return self.sgp.elbo().numpy() # return the ELBO
69
-
70
-
71
- def get_greedy_sgp_sol(num_sensors, candidates, X_train, noise_variance, kernel,
72
- transform=None):
73
- """Get sensor placement solutions using the Greedy-SGP method. Uses a greedy algorithm to
74
- select sensor placements from a given discrete set of candidates locations.
75
-
76
- Refer to the following papers for more details:
77
- - Efficient Sensor Placement from Regression with Sparse Gaussian Processes in Continuous and Discrete Spaces [[Jakkala and Akella, 2023](https://www.itskalvik.com/publication/sgp-sp/)]
78
-
79
- Args:
80
- num_sensors (int): Number of sensor locations to optimize
81
- candidates (ndarray): (n, d); Candidate sensor placement locations
82
- X_train (ndarray): (n, d); Locations in the environment used to approximate the monitoring regions
83
- noise_variance (float): data variance
84
- kernel (gpflow.kernels.Kernel): gpflow kernel function
85
- transform (Transform): Transform object
86
-
87
- Returns:
88
- Xu (ndarray): (m, d); Solution sensor placement locations
89
- """
90
- sgp_model = GreedySGP(num_sensors, candidates, X_train,
91
- noise_variance, kernel, transform=transform)
92
- model = CustomSelection(num_sensors,
93
- sgp_model.bound,
94
- optimizer='naive',
95
- verbose=False)
96
- sol = model.fit_transform(np.arange(len(candidates)).reshape(-1, 1))
97
- return candidates[sol.reshape(-1)]
@@ -1,39 +0,0 @@
1
- Metadata-Version: 2.2
2
- Name: sgptools
3
- Version: 1.2.0
4
- Summary: Software Suite for Sensor Placement and Informative Path Planning
5
- Home-page: https://www.itskalvik.com/sgp-tools
6
- Author: Kalvik
7
- Author-email: itskalvik@gmail.com
8
- License: Apache-2.0
9
- Requires-Python: >=3.6
10
- License-File: LICENSE.txt
11
- Requires-Dist: apricot-select
12
- Requires-Dist: matplotlib
13
- Requires-Dist: pandas
14
- Requires-Dist: scikit-learn
15
- Requires-Dist: scipy
16
- Requires-Dist: numpy<2.0.0
17
- Requires-Dist: ortools
18
- Requires-Dist: scikit-image
19
- Requires-Dist: shapely
20
- Requires-Dist: cma
21
- Requires-Dist: bayesian-optimization
22
- Requires-Dist: hkb_diamondsquare
23
- Requires-Dist: tensorflow-probability[tf]>=0.21.0
24
- Requires-Dist: tensorflow>=2.13.0; platform_machine != "arm64"
25
- Requires-Dist: tensorflow-aarch64>=2.13.0; platform_machine == "arm64"
26
- Requires-Dist: tensorflow-macos>=2.13.0; platform_system == "Darwin" and platform_machine == "arm64"
27
- Requires-Dist: typing_extensions
28
- Requires-Dist: gpflow>=2.7.0
29
- Requires-Dist: pillow
30
- Dynamic: author
31
- Dynamic: author-email
32
- Dynamic: description
33
- Dynamic: home-page
34
- Dynamic: license
35
- Dynamic: requires-dist
36
- Dynamic: requires-python
37
- Dynamic: summary
38
-
39
- Software Suite for Sensor Placement and Informative Path Planning